Corpus preparation

We use here data from Media cloud with only title of news. Therfore the program is more simple than in course.

Selection of media

# Load data with the function fread (fast) and the encoding UTF-8
df1<-fread("data/source/en_GBR_guardi_2019_2020.csv", encoding = "UTF-8")
df1$media<-"en_GBR_guardi"


df2<-fread("data/source/en_GBR_telegr_2019_2020.csv", encoding = "UTF-8")
df2$media<-"en_GBR_telegr"


# transform in data.table format
df<-rbind(df1,df2)
rm(df1,df2)


# select column of interest
df$id <- df$stories_id
df$who <- df$media
df$when <- as.Date(df$publish_date)
df$text <- df$title
df<-df[,c("id","who","when","text")]
df<-df[order(when),]

# select period of interest
mintime<-as.Date("2019-01-01")
maxtime<-as.Date("2020-12-31")
df<-df[(is.na(df$when)==F),] 
df<-df[as.Date(df$when) >= mintime,]
df<-df[as.Date(df$when) <= maxtime,]

# eliminate duplicate
df<-df[duplicated(df$text)==F,]

Check of time frequency

Time divisions

We transform the previous data.frame in a data.table format for easier operations of aggregation

dt<-as.data.table(df)
dt$day     <- as.Date(dt$when)
dt$week    <- cut(dt$day, "weeks", start.on.monday=TRUE)
dt$month   <- cut(dt$day, "months")
dt$weekday <- weekdays(dt$day)

# Save data frame
saveRDS(dt,"data/corpus/dt_mycorpus.RDS") 

News by week

We examine if the distribution is regular by week for the different media of the corpus.

dt<-readRDS("data/corpus/dt_mycorpus.RDS")
news_weeks<-dt[,.(newstot=.N),by=.(week,who)]

p<-ggplot(news_weeks, aes(x=as.Date(week),y=newstot, col=who))+
   geom_line()+
   geom_smooth(method = 'loess', formula = 'y~x')+
   scale_y_continuous("Number of news", limits = c(0,NA)) +
   scale_x_date("Week (starting on monday)") +
         ggtitle(label ="Corpus : distribution of news by week")
p

News by weekday

We examine if the distribution is regular by weekday and check in particular the effect of the week-end.

#compute frequencies by weekday
news_weekdays<-dt[,.(newstot=.N),by=.(weekday,who)]
news_weekdays<-news_weekdays[,.(weekday,newspct=100*newstot/sum(newstot)),by=.(who)]


# Translate weekdays in english and order
news_weekdays$weekday<-as.factor(news_weekdays$weekday)
levels(news_weekdays$weekday)<-c("7.Sunday","4.Wednesday","1.Monday","2.Tuesday","3.Thursday","6.Sathurday","5.Friday")
news_weekdays$weekday<-as.factor(as.character(news_weekdays$weekday))
news_weekdays<-news_weekdays[order(news_weekdays$weekday),]


p<-ggplot(news_weekdays, aes(x=weekday,fill = who, y=newspct))+
         geom_bar(position = "dodge", stat="identity")+
         scale_y_continuous("Share of news (%)", limits = c(0,NA)) +
         ggtitle(label ="Corpus : distribution of news by week day")
p

Transform in quanteda corpus


# transform in quanteda
qd<-corpus(dt,docid_field = "id",text_field = "text")


# filter by number of tokens by sentence
qd$nbt<-ntoken(texts(qd))
qd<-corpus_subset(qd, nbt<100)
qd<-corpus_subset(qd, nbt>2)



# Save corpus in qd format
saveRDS(qd,"data/corpus/qd_mycorpus.RDS")



head(qd)
Corpus consisting of 6 documents and 7 docvars.
1128777462 :
"Apprehension on all sides before launch of Irish abortion se..."

1128777446 :
"Toxic legacy taints ANC as it nears 25-year rule in South Af..."

1128795170 :
"Preparations for the Harbin ice and snow festival – in pictu..."

1128795156 :
"'Resign from Facebook': experts offer Mark Zuckerberg advice..."

1128795141 :
"Gender pay gap: 2018 brought transparency – will 2019 bring ..."

1128795146 :
"It's a thong thing: why £16 flip-flops are the shoe of summe..."
summary(qd,3)
Corpus consisting of 103386 documents, showing 3 documents:

       Text Types Tokens Sentences           who       when        day       week      month
 1128777462    10     10         1 en_GBR_guardi 2019-01-01 2019-01-01 2018-12-31 2019-01-01
 1128777446    12     12         1 en_GBR_guardi 2019-01-01 2019-01-01 2018-12-31 2019-01-01
 1128795170    11     11         1 en_GBR_guardi 2019-01-01 2019-01-01 2018-12-31 2019-01-01
 weekday nbt
   Mardi  10
   Mardi  12
   Mardi  11

Geographical tags

Preparation of data

Load dictonary

We start by loading the last version of the Imageun dictionary and we extract our target language (here : english).

# Load multilanguage dictionary
dict<-fread("data/dico_states/global_state_V2.csv")

# Select french dictionary
dict <- dict[dict$lang=="en",]


head(dict)

Load corpus

qd <- readRDS("data/corpus/qd_mycorpus.RDS")

Load tagging function

extract_tags <- function(qd = qd,                      # the corpus of interest
                         lang = "fr",                  # the language to be used
                         dict = dict,                  # the dictionary of target 
                         code = "ISO3" ,                # variable used for coding
                         alias = "x",                   # variable used for alias
                         tagsname = "states",           # name of the tags column
                         split  = c("'","’","-"),       # split list
                         tolow = TRUE  ,                # Tokenize text
                         comps = c("Afrique du sud")  # compounds
                         )
{ 


  
# Tokenize  
x<-as.character(qd)


if(length(split) > 0) { reg<-paste(split, collapse = '|')
                       x <- gsub(reg," ",x)}  
if(tolow) { x <- tolower(x)} 
toks<-tokens(x)

# compounds
if(length(split) > 0) { reg<-paste(split, collapse = '|')
                       comps<- gsub(reg," ",comps)}  
if(tolow)       {comps <- tolower(comps)}  
toks<-tokens_compound(toks,pattern=phrase(comps))

  
# Load dictionaries and create compounds

  ## Target dictionary

labels <-dict[[alias]]
if(length(split) > 0) { reg<-paste(split, collapse = '|')
                       labels<- gsub(reg," ",labels)}  
if(tolow)       {labels <- tolower(labels)}  
toks<-tokens_compound(toks,pattern=phrase(labels))
  
 # create quanteda dictionary
keys <-gsub(" ","_",labels)
qd_dict<-as.list(keys)
names(qd_dict)<-dict[[code]]
qd_dict<-dictionary(qd_dict,tolower = FALSE)

# Identify geo tags (states or reg or org ...)
toks_tags <- tokens_lookup(toks, qd_dict, case_insensitive = F)
toks_tags <- lapply(toks_tags, unique)
toks_tags<-as.tokens(toks_tags)
list_tags<-function(x){res<-paste(x, collapse=' ')}
docvars(qd)[[tagsname]]<-as.character(lapply(toks_tags,FUN=list_tags))
docvars(qd)[[paste("nb",tagsname,sep="")]]<-ntoken(toks_tags)



# Export results
return(qd)
 }

Geographical annotation

Annotate all entities

t1<-Sys.time()

frcomps<-c("China sea", "indian ocean", "american continent", "american countries")

qd <- extract_tags (qd = qd,
                     lang="en",
                     dict = dict,
                     code = "ISO3",
                     alias = "x",
                     tagsname = "states",
                     split = c("'","’","-"),
                     comps = frcomps,
                     tolow = TRUE)

t2 = Sys.time()
paste("Program executed in ", t2-t1)

table(qd$nbstates)

check news with maximum state number

table(qd$nbstates)

    0     1     2     3     4     5 
76465 23942  2752   195    30     2 
check<-corpus_subset(qd,nbstates>3)
x<-data.frame(who=check$who,when = check$when,text=as.character(check),states=check$states,nbstates=check$nbstates)
x<-x[order(x$nbstates,decreasing = T),]
kable(x)
who when text states nbstates
1400389059 en_GBR_guardi 2019-09-23 France, Germany and UK blame Iran for Saudi oilfield attack FRA DEU GBR IRN SAU 5
1694147850 en_GBR_telegr 2020-08-27 travel news quarantine italy greece france switzerland uk restrictions ITA GRC FRA CHE GBR 5
1282834562 en_GBR_guardi 2019-05-14 No Iran threat in Syria or Iraq, top British officer says, contradicting US IRN SYR IRQ GBR 4
1313975713 en_GBR_guardi 2019-06-17 South Korea v Norway, Nigeria v France: Women’s World Cup clockwatch – live! KOR NOR NGA FRA 4
1315886333 en_GBR_guardi 2019-06-19 UK, France and Germany in last-ditch effort to save Iran deal GBR FRA DEU IRN 4
1384226853 en_GBR_guardi 2019-09-05 Euro 2020: Republic of Ireland v Switzerland, Romania v Spain – live! IRL CHE ROU ESP 4
1386655906 en_GBR_guardi 2019-09-08 Euro 2020 qualifying: Sweden v Norway, Finland v Italy and more – live! SWE NOR FIN ITA 4
1419597904 en_GBR_guardi 2019-10-15 Switzerland v Republic of Ireland, Sweden v Spain: Euro 2020 – live! CHE IRL SWE ESP 4
1517113825 en_GBR_telegr 2019-12-27 Iran, Russia and China carry out naval drills in Indian Ocean IRN RUS CHN IND 4
1517033930 en_GBR_telegr 2020-01-03 ‘Westerners should leave UAE immediately’: Gulf warning as British troops put at greater risk in Iraq after US kills Iran military chief ARE GBR IRQ IRN 4
1597961952 en_GBR_telegr 2020-03-31 Britain, France and Germany bypass US sanctions to provide Iran with medical aid GBR FRA DEU IRN 4
1682896357 en_GBR_telegr 2020-07-22 Australia joins US and Japan for navy drills in the Philippine Sea as concerns grow over China AUS JPN PHL CHN 4
1738190141 en_GBR_telegr 2020-07-26 France and Germany could join Spain on UK’s quarantine list FRA DEU ESP GBR 4
1671038069 en_GBR_telegr 2020-07-28 Chinese foreign ministry to suspend ‘mutual legal assistance’ in criminal matters between Hong Kong and the UK, Australia and Canada CHN GBR AUS CAN 4
1708910479 en_GBR_telegr 2020-08-06 UK quarantine imposed for travellers from Belgium, Andorra and the Bahamas GBR BEL AND BHS 4
1683099252 en_GBR_guardi 2020-08-14 Iran and Turkey denounce UAE over deal with Israel IRN TUR ARE ISR 4
1687591712 en_GBR_guardi 2020-08-19 Australian pilots drafted to help fly UK drones over Syria and Iraq AUS GBR SYR IRQ 4
1703180856 en_GBR_telegr 2020-08-25 Switzerland to have Covid quarantine re-imposed as Czech republic, Jamaica and Iceland enter danger zone CHE CZE JAM ISL 4
1695194722 en_GBR_telegr 2020-08-28 Travel news: Switzerland, Czech Republic and Jamaica out, but Cuba in following quarantine update CHE CZE JAM CUB 4
1707661716 en_GBR_guardi 2020-09-10 UK, France and Germany agree to reject US demand for Iran snapback sanctions GBR FRA DEU IRN 4
1719242458 en_GBR_guardi 2020-09-23 UK, France and Germany summon Iranian ambassadors over prisoners GBR FRA DEU IRN 4
1766046522 en_GBR_telegr 2020-09-27 UAE, Bahrain defend ties with Israel as Palestinians cry betrayal ARE BHR ISR PSE 4
1726187015 en_GBR_telegr 2020-10-01 Travel latest news: Italy, Greece, Sweden and Poland face the chop as quarantine decision looms ITA GRC SWE POL 4
1775693184 en_GBR_telegr 2020-10-01 Turkey and Poland put on quarantine list as Greece and Italy escape TUR POL GRC ITA 4
1737834173 en_GBR_guardi 2020-10-13 Nations League: Ukraine shock Spain, Germany and Switzerland draw thriller UKR ESP DEU CHE 4
1760080346 en_GBR_guardi 2020-11-04 Coronavirus live news: England to begin lockdown as Russia, Poland, Switzerland, Austria see record case rises RUS POL CHE AUT 4
1766818486 en_GBR_guardi 2020-11-11 Netherlands v Spain, Germany v Czech Republic and more: international football – live! NLD ESP DEU CZE 4
1767020618 en_GBR_guardi 2020-11-11 International friendly roundup: Finland stun France and Belgium edge Swiss FIN FRA BEL CHE 4
1772280029 en_GBR_guardi 2020-11-17 Spain v Germany, Croatia v Portugal and more: Nations League – live! ESP DEU HRV PRT 4
1777984778 en_GBR_guardi 2020-11-23 UK, France and Germany discuss working with Joe Biden on Iran nuclear deal GBR FRA DEU IRN 4
1784491580 en_GBR_guardi 2020-12-01 France and New Zealand join Australia’s criticism of Chinese government tweet FRA NZL AUS CHN 4
1802693210 en_GBR_guardi 2020-12-20 Belgium, Italy and Netherlands ban flights from UK over new Covid strain BEL ITA NLD GBR 4

Save geographically anotated corpus

saveRDS(qd,"data/corpus/qd_mycorpus_states.RDS")
paste("Size of resulting file = ",round(file.size("data/corpus/qd_mycorpus_states.RDS")/1000000,3), "Mo")
[1] "Size of resulting file =  5.721 Mo"

Thematic tags

qd <-readRDS("data/corpus/qd_mycorpus_states.RDS")

Load tagging function

extract_tags <- function(qd = qd,                      # the corpus of interest
                         lang = "fr",                  # the language to be used
                         dict = dict,                  # the dictionary of target 
                         code = "code" ,                # variable used for coding
                         alias = "alias",               # variable used for alias
                         tagsname = "states",           # name of the tags column
                         split  = c("'","’","-"),       # split list
                         tolow = TRUE  ,                # Tokenize text
                         comps = c("Afrique du sud")  # compounds
                         )
{ 


  
# Tokenize  
x<-as.character(qd)


if(length(split) > 0) { reg<-paste(split, collapse = '|')
                       x <- gsub(reg," ",x)}  
if(tolow) { x <- tolower(x)} 
toks<-tokens(x)

# compounds
if(length(split) > 0) { reg<-paste(split, collapse = '|')
                       comps<- gsub(reg," ",comps)}  
if(tolow)       {comps <- tolower(comps)}  
toks<-tokens_compound(toks,pattern=phrase(comps))

  
# Load dictionaries and create compounds

  ## Target dictionary

labels <-dict[[alias]]
if(length(split) > 0) { reg<-paste(split, collapse = '|')
                       labels<- gsub(reg," ",labels)}  
if(tolow)       {labels <- tolower(labels)}  
toks<-tokens_compound(toks,pattern=phrase(labels))
  
 # create quanteda dictionary
keys <-gsub(" ","_",labels)
qd_dict<-as.list(keys)
names(qd_dict)<-dict[[code]]
qd_dict<-dictionary(qd_dict,tolower = FALSE)

# Identify geo tags (states or reg or org ...)
toks_tags <- tokens_lookup(toks, qd_dict, case_insensitive = F)
toks_tags <- lapply(toks_tags, unique)
toks_tags<-as.tokens(toks_tags)
list_tags<-function(x){res<-paste(x, collapse=' ')}
docvars(qd)[[tagsname]]<-as.character(lapply(toks_tags,FUN=list_tags))
docvars(qd)[[paste("nb",tagsname,sep="")]]<-ntoken(toks_tags)



# Export results
return(qd)
 }

The pandemic topic

Dictionary

We decide here to use lower case transformation. We use a star for the words that can take a plural form. We had of course covid and coronavirus

label <- c("pandemic*", "epidemic*", "virus", "world health organisation", "ebola",  "h1n1","sras", "chikungunya", "cholera", "flu", "covid*","coronavirus")
code  <- rep("pand", length(label))
lang  <- rep("en", length(label))
dict_pande <- data.frame(code,lang,label)
kable(dict_pande)
code lang label
pand en pandemic*
pand en epidemic*
pand en virus
pand en world health organisation
pand en ebola
pand en h1n1
pand en sras
pand en chikungunya
pand en cholera
pand en flu
pand en covid*
pand en coronavirus

encomps<-c("computer virus")

Annotation

qd <- extract_tags (qd = qd,
                     lang="en",
                     dict = dict_pande,
                     code = "code",
                     alias = "label",
                    tagsname = "pand",
                     split = c("'","’","-"),
                     comps = encomps,
                     tolow = TRUE)

table(qd$nbpand)

saveRDS(qd,"data/corpus/qd_mycorpus_states_topic.RDS")

Visualization

x<-data.table(docvars(qd))
x$tag<-x$nbpand !=0
tab<-x[,.(tot=.N),by=.(month,tag, who)]
tab<-tab[tab$tag==TRUE,]
tab$month<-as.Date(tab$month)

       
       p<-ggplot(tab, aes(x=month,fill =who, y=tot))+
         geom_bar(stat="identity")+
         ggtitle(label ="Pandemic : distribution of tags by month and media")
p

Hypercubes creation

Aggregation function


#' @title create an hypercube
#' @name hypercube
#' @description create a network of interlinked states
#' @param corpus a corpus of news in quanteda format
#' @param order an order of sentences in the news
#' @param who the source dimension
#' @param when the time dimension
#' @param timespan aggreation of time
#' @param what a list of topics
#' @param where1 a list of states
#' @param where2  a list of states


hypercube   <- function( corpus = qd,
                        order = "order",
                        who = "source",
                        when = "when",
                        timespan = "week",
                        what = "what",
                        where1 = "where1",
                        where2 = "where2")
{


  
# prepare data

  don<-docvars(corpus)
  
  df<-data.table(id     = docid(corpus),
                 order  = don[[order]],
                 who    = don[[who]],
                 when   = don[[when]],
                 what   = don[[what]],
                 where1 = don[[where1]],
                 where2 = don[[where2]])

  # adjust id
 df$id<-paste(df$id,"_",df$order,sep="")
 
# change time span
  df$when<-as.character(cut(as.Date(df$when), timespan, start.on.monday = TRUE))

# unnest where1
  df$where1[df$where1==""]<-"_no_"
  df<-unnest_tokens(df,where1,where1,to_lower=F)
  
# unnest where2
  df$where2[df$where2==""]<-"_no_"
  df<-unnest_tokens(df,where2,where2,to_lower=F) 
  
# unnest what
  df$what[df$what==""]<-"_no_"
  df<-unnest_tokens(df,what,what,to_lower=F) 
  


# Compute weight of news
  newswgt<-df[,list(wgt=1/.N),list(id)]
  df <- merge(df,newswgt, by="id")


# ------------------------ Hypercube creation --------------------#
  
  
# Aggregate
  hc<- df[,.(tags = .N, news=sum(wgt)) ,.(order,who, when,where1,where2, what)]
  
# Convert date to time
  hc$when<-as.Date(hc$when)
  
# export
  return(hc)
  
}

Pandemic hypercube

qd<-readRDS("data/corpus/qd_mycorpus_states_topic.RDS")
qd$order<-1

hc<-hypercube( corpus   = qd,
                    order    = "order",
                    who      = "who",
                    when     = "when",
                    timespan = "day",
                    what     = "pand",
                    where1   = "states",
                    where2   = "states")

saveRDS(hc,"data/corpus/hc_mycorpus_states_pand.RDS")
paste("Size of resulting file = ",round(file.size("data/corpus/hc_mycorpus_states_pand.RDS")/1000000,3), "Mo")
[1] "Size of resulting file =  0.133 Mo"

Hypercubes exploration

#### ---------------- testchi2 ----------------
#' @title  Compute the average salience of the topic and test significance of deviation
#' @name what
#' @description create a table and graphic of the topic
#' @param tabtest a table with variable trial, success and null.value
#' @param minsamp : Threshold of sample size requested for salience computation
#' @param mintest : Threshold of estimated value requested for chi-square test


testchi2<-function(tabtest=tabtest,
                   minsamp = 20,
                   mintest = 5) 
{
  tab<-tabtest
  n<-dim(tab)[1]
  
  # Compute salience if sample size sufficient (default : N>20)
  tab$estimate <-NA
  tab$salience <-NA
  tab$chi2<-NA
  tab$p.value<-NA
  if (tab$trial > minsamp){ tab$estimate<-round(tab$success/tab$trial,5)
  tab$salience<-tab$estimate/tab$null.value
  
  # Chi-square test if estimated value sufficient (default : Nij* > 5)
  
  for (i in 1:n) {
    if(tab$trial[i]*tab$null.value[i]>=mintest) {  
      test<-prop.test(x=tab$success[i],n=tab$trial[i], p=tab$null.value[i], 
                      alternative = "greater")
      tab$chi2[i]<-round(test$statistic,2)
      tab$p.value[i]<-round(test$p.value,5)
    } 
  }
  }
  return(tab)
}
hc <- readRDS("data/corpus/hc_mycorpus_states_pand.RDS")

Topic frequence (What ?)

Function

### ---------------- what ----------------
#' @title  Compute the average salience of the topic
#' @name what
#' @description create a table and graphic of the topic
#' @param hc an hypercube prepared as data.table
#' @param subtop a subtag of the main tag (default = NA)
#' @param title Title of the graphic


what <- function (hc = hypercube,
                  subtop = NA,
                  title = "What ?")
{
 
  
tab<-hc
if (is.na(subtop)){tab$what <-tab$what !="_no_"}else {tab$what <- tab$what == subtop}

tab<-tab[,list(news = sum(news)),by = what]
tab$pct<-100*tab$news/sum(tab$news)

p <- plot_ly(tab,
             labels = ~what,
             values = ~pct,
             type = 'pie') %>%
  layout(title = title,
         xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
         yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))

output<-list("table" = tab, "plotly" =p)

return(output)

}

Example

res_what <- what(hc = hc,
             subtop = NA,
             title = "Topic news")
res_what$table
res_what$plotly

Topic variation by media (who.what)

The function who.what explore the variation of interest for the topic in the different media of the corpus.

Function


#### ---------------- who.what ----------------
#' @title  visualize variation of the topic between media
#' @name who.what
#' @description create a table of variation of the topic by media
#' @param hc an hypercube prepared as data.table
#' @param test : visualize test (TRUE) or salience (FALSE)
#' @param minsamp : Threshold of sample size requested for salience computation
#' @param mintest sample size of estimate for chi-square test (default = 5)
#' @param title Title of the graphic


who.what <- function (hc = hypercube,
                      test = FALSE,
                      minsamp = 20,
                      mintest = 5,
                      title = "Who says What ?")
{
  
  tab<-hc
  {tab$what <-tab$what !="_no_"}
  
  tab<-tab[,list(trial = sum(news),success=round(sum(news*what),0)),by = list(who)]
  ref <-round(sum(tab$success)/sum(tab$trial),4)
  tab$null.value<-ref
  
  tab<-testchi2(tabtest=tab,
                minsamp = minsamp,
                mintest = mintest)
  
  
  
  if (test==FALSE) {tab$index =tab$salience
  tab<-tab[tab$trial > minsamp,]
  mycol<-brewer.pal(7,"YlOrRd")
  } 
  else {tab$index=tab$p.value
  tab<-tab[tab$trial*tab$null.value>mintest,]
  mycol<-brewer.pal(7,"RdYlBu")
  mycol[4]<-"lightyellow"
  }
  
  p <- plot_ly(tab,
               x = ~who,
               y = ~estimate*100,
               color= ~index,
               colors= mycol,
               hoverinfo = "text",
               text = ~paste('Source: ',who,
                             '<br /> Total news  : ', round(trial,0),
                             '<br /> Topic news : ', round(success,0),
                             '<br /> % observed  : ', round(estimate*100,2),'%',
                             '<br /> % estimated : ', round(null.value*100,2),'%',
                             '<br /> Salience : ', round(salience,2),  
                             '<br /> p.value : ', round(p.value,4)),
               type = "bar")  %>%
    layout(title = title,
           yaxis = list(title = "% news"),
           barmode = 'stack')
  
  output<-list("table" = tab, "plotly" =p)
  
  return(output)
  
}

Example





res_who_what<- who.what(hc=hc, 
                        test = TRUE,
                        minsamp = 5,
                        mintest = 1,
                        title = "Topic news by media - Significance")
res_who_what$plotly
NA

Topic variation through time (when.what)

Function

#### ---------------- when.what ----------------
#' @title  visualize variation of the topic through time
#' @name when.what
#' @description create a table of variation of the topic by media
#' @param test : visualize test (TRUE) or salience (FALSE)
#' @param minsamp : Threshold of sample size requested for salience computation
#' @param mintest sample size of estimate for chi-square test (default = 5)
#' @param title Title of the graphic


when.what <- function (hc = hypercube,
                       test = FALSE,
                       minsamp = 20,
                       mintest = 5,
                       title = "Who says What ?")
{
  
  tab<-hc
  {tab$what <-tab$what !="_no_"}
  
  tab<-tab[,list(trial = sum(news),success=round(sum(news*what),0)),by = list(when)]
  ref <-round(sum(tab$success)/sum(tab$trial),4)
  tab$null.value<-ref
  
  tab<-testchi2(tabtest=tab,
                minsamp = minsamp,
                mintest = mintest)
  
  if (test==FALSE) {tab$index =tab$salience
  tab<-tab[tab$trial > minsamp,]
  mycol<-brewer.pal(7,"YlOrRd")
  } 
  else {tab$index=tab$p.value
  tab<-tab[tab$trial*tab$null.value>mintest,]
  mycol<-brewer.pal(7,"RdYlBu")
  mycol[4]<-"lightyellow"
  }
  
  
  p <- plot_ly(tab,
               x = ~as.character(when),
               y = ~estimate*100,
               color= ~index,
               colors= mycol,
               hoverinfo = "text",
               text = ~paste('Time: ',when,
                             '<br /> Total news  : ', round(trial,0),
                             '<br /> Topic news : ', round(success,0),
                             '<br /> % observed  : ', round(estimate*100,2),'%',
                             '<br /> % estimated : ', round(null.value*100,2),'%',
                             '<br /> Salience : ', round(salience,2),  
                             '<br /> p.value : ', round(p.value,4)),
               type = "bar")  %>%
    layout(title = title,
           yaxis = list(title = "% news"),
           barmode = 'stack')
  
  output<-list("table" = tab, "plotly" =p)
  
  return(output)
  
}

Example 1 : 2019-2020 by month

# Modify time period by month
hc2 <- hc %>% mutate(when = cut(when,breaks="month"))


res_when_what<- when.what(hc=hc2, 
                          test=TRUE,
                          minsamp=10,
                          mintest=5,
                          title = "Topic news by month - Significance")


res_when_what$plotly

Example 2 : 2020 by week

# Modify time period by month
hc2 <- hc %>% filter(substr(when,1,4)=="2020") %>% mutate(when = cut(when,breaks="week"))


res_when_what<- when.what(hc=hc2, 
                          test=TRUE,
                          minsamp=10,
                          mintest=5,
                          title = "Topic news by week - Significance")


res_when_what$plotly

Example 3 : Jan-March 2020 by day

# Modify time period by month
hc2 <- hc %>% filter(when > as.Date("2020-01-01"), when < as.Date("2020-04-01")) 


res_when_what<- when.what(hc=hc2, 
                          test=TRUE,
                          minsamp=10,
                          mintest=5,
                          title = "Topic news by day - Significance")


res_when_what$plotly

Topic variation through space (where.what)

Function


#### ---------------- where.what ----------------
#' @title  visualize spatialization of the topic 
#' @name where.what
#' @description create a table of variation of the topic by media
#' @param hc an hypercube prepared as data.table
#' @param test : visualize test (TRUE) or salience (FALSE)
#' @param minsamp : Threshold of sample size requested for salience computation
#' @param mintest sample size of estimate for chi-square test (default = 5)
#' @param map a map with coordinates in lat-long
#' @param proj a projection accepted by plotly
#' @param title Title of the graphic


where.what <- function (hc = hypercube,
                        test = FALSE,
                        minsamp = 20,
                        mintest = 5,
                        map = world_ctr,
                        proj = 'azimuthal equal area',
                        title = "Where said What ?")
{
  
  tab<-hc
  tab$what <-tab$what !="_no_"
  
  tab<-tab[,list(trial = round(sum(news),0),success=round(sum(news*what),0)),by = list(where1)]
  ref <-round(sum(tab$success)/sum(tab$trial),4)
  tab$null.value<-ref
  
  tab<-testchi2(tabtest=tab,
                minsamp = minsamp,
                mintest = mintest)
  
  
  
  tab<-tab[order(-chi2),]
  
  
  
  if (test==FALSE) {tab$index =tab$salience
  tab<-tab[tab$trial > minsamp,]
  mycol<-brewer.pal(7,"YlOrRd")
  } 
  else {tab$index=tab$p.value
  tab<-tab[tab$trial*tab$null.value>mintest,]
  mycol<-brewer.pal(7,"RdYlBu")
  mycol[4]<-"lightyellow"
  }
  
  
  map<-merge(map,tab,all.x=T,all.y=F,by.x="ISO3",by.y="where1")
  
  
  
  #map2<-map[is.na(map$pct)==F,]
  #map2<-st_centroid(map2)
  #map2<-st_drop_geometry(map2)
  
  
  g <- list(showframe = TRUE,
            framecolor= toRGB("gray20"),
            coastlinecolor = toRGB("gray20"),
            showland = TRUE,
            landcolor = toRGB("gray50"),
            showcountries = TRUE,
            countrycolor = toRGB("white"),
            countrywidth = 0.2,
            projection = list(type = proj))
  
  
  
  p<- plot_geo(map)%>%
    add_markers(x = ~lon,
                y = ~lat,
                sizes = c(0, 250),
                size = ~success,
                #             color= ~signif,
                color = ~index,
                colors= mycol,
                hoverinfo = "text",
                text = ~paste('Location: ',NAME,
                              '<br /> Total news  : ', round(trial,0),
                              '<br /> Topic news : ', round(success,0),
                              '<br /> % observed  : ', round(estimate*100,2),'%',
                              '<br /> % estimated : ', round(null.value*100,2),'%',
                              '<br /> Salience : ', round(salience,2),  
                              '<br /> p.value : ', round(p.value,4))) %>%
    
    layout(geo = g,
           title = title)
  
  
  
  output<-list("table" = tab, "plotly" =p)
  
  return(output)
  
}

Example

map<-readRDS("data/dico_states/world_ctr_4326.Rdata")
hc2<-hc %>% filter(where1 !="_no_", where2 !="_no_") %>% filter(where1 !="GBR", where2 !="GBR")

res_where_what<- where.what(hc=hc2,
                            test=TRUE,
                            minsamp=10,
                            map = map, 
                            mintest =2,
                            title = "Topic news by states - Significance")
res_where_what$plotly

Spatial Networks

Geo networks modelisation

Hypercube Filter (function)

hc_filter <- function(don = hc,
                      who = "who",
                      when = "when",
                      where1 = "where1",
                      where2 = "where2",
                      wgt = "tags",
                      self = FALSE,
                      when_start = NA,
                      when_end = NA,
                      who_exc = NA,
                      who_inc = NA,
                      where1_exc = NA,
                      where1_inc = NA,
                      where2_exc = NA,
                      where2_inc = NA)

  {                          
  
    df<-data.table(who = don[[who]],
                   when = don[[when]],
                   where1 = don[[where1]],
                   where2 = don[[where2]],
                   wgt = don[[wgt]])
    
    # Select time period
        if (is.na(when_start)==FALSE) { 
        df <- df[when >= as.Date(when_start), ]}
        if (is.na(when_end)==FALSE) { 
        df <- df[when <= as.Date(when_end), ]}
    # Select who
        if (is.na(who_exc)==FALSE) { 
        df <- df[!(who %in% who_exc), ]}
        if (is.na(who_inc)==FALSE) { 
        df <- df[(who %in% who_inc), ]}
    # Select where1
        if (is.na(where1_exc)==FALSE) { 
        df <- df[!(where1 %in% where1_exc), ]}
        if (is.na(where1_inc)==FALSE) { 
        df <- df[(where1 %in% where1_inc), ]}
    # Select where2
        if (is.na(where2_exc)==FALSE) { 
        df <- df[!(where2 %in% where2_exc), ]}
        if (is.na(where2_inc)==FALSE) { 
        df <- df[(where2 %in% where2_inc), ]}
    # eliminate internal links
       if (self==FALSE) { 
        df <- df[(where1 != where2), ]}
    return(df)
  
}

Matrix builder (function)

build_int <- function(don = don,       # a dataframe with columns i, j , Fij
                      i = "where1",
                      j = "where2",
                      Fij = "wgt",
                      s1 = 10,
                      s2 = 10,
                      n1 = 2,
                      n2 = 2,
                      k = 1)

{  
  df<-data.table(i=don[[i]],j=don[[j]],Fij=don[[Fij]])
  int <-df[,.(Fij=sum(Fij)),.(i,j)]
  int<-dcast(int,formula = i~j,fill = 0)
  mat<-as.matrix(int[,-1])
  row.names(mat)<-int$i
  mat<-mat[apply(mat,1,sum)>=s1,apply(mat,2,sum)>=s2 ]
  m0<-mat
  m0[m0<k]<-0
  m0[m0>=k]<-1
  mat<-mat[apply(m0,1,sum)>=n1,apply(m0,2,sum)>=n2 ]
  int<-reshape2::melt(mat)
  names(int) <-c("i","j","Fij")
  return(int)
}

Random model (function)


rand_int <- function(int = int, # A table with columns i, j Fij
                     maxsize = 100000,
                     diag    = FALSE,
                     resid   = FALSE) {
    # Eliminate diagonal ?
    if (diag==FALSE) { 
        int <- int[as.character(int$i) != as.character(int$j), ]}
  
    # Compute model if size not too large
    if (dim(int)[1] < maxsize) {
       # Proceed to poisson regression model
       mod <- glm( formula = Fij ~ i + j,family = "poisson", data = int)
  
       # Add residuals if requested
       if(resid == TRUE)   { 
          # Add estimates
          int$Eij <- mod$fitted.values

          # Add absolute residuals
          int$Rabs_ij <- int$Fij-int$Eij

          # Add relative residuals
          int$Rrel_ij <- int$Fij/int$Eij

          # Add chi-square residuals
          int$Rchi_ij <-  (int$Rabs_ij)**2 / int$Eij
          int$Rchi_ij[int$Rabs_ij<0]<- -int$Rchi_ij[int$Rabs_ij<0]
          }
         
    } else { paste ("Table > 100000 -  \n 
                     modify maxsize =  parameter \n
                     if you are sure that your computer can do it !")}
  # Export results
  int$i<-as.character(int$i)
  int$j<-as.character(int$j)
  return(int)
  
 }

Visualize network (function)

geo_network<- function(don = don,
                       from = "i",
                        to = "j", 
                        size = "Fij",
                        minsize = 1,
                        maxsize = NA,
                        test = "Fij",
                        mintest = 1,
                        loops  = FALSE, 
                        title = "Network")

{
int<-data.frame(i = as.character(don[,from]),
                j = as.character(don[,to]),
                size = don[,size],
                test = don[,test]
                )
if (is.na(minsize)==FALSE) {int =int[int$size >= minsize,]} 
if (is.na(maxsize)==FALSE) {int =int[int$size <= maxsize,]} 
if (is.na(mintest)==FALSE) {int =int[int$test >= mintest,]}

nodes<-data.frame(code = unique(c(int$i,int$j)))
nodes$code<-as.character(nodes$code)
nodes$id<-1:length(nodes$code)
nodes$label<-nodes$code
nodes$color <-"gray"
nodes$color[nodes$code %in% int$j]<-"red"


# Adjust edge codes
edges <- int %>% mutate(width = 5+5*size / max(size)) %>%
                left_join(nodes %>% select(i=code, from = id)) %>%  
                left_join(nodes %>% select(j=code, to = id )) 

# compute nodesize
toti<-int %>% group_by(i) %>% summarize(size =sum(size)) %>% select (code=i,size)
totj<-int %>% group_by(j) %>% summarize(size =sum(size)) %>% select (code=j,size)
tot<-rbind(toti,totj)
tot<-unique(tot)
tot$code<-as.factor(tot$code)
nodes <- left_join(nodes,tot) %>% mutate(value = 1 +5*sqrt(size/max(size)))


#sel_nodes <-nodes %>% filter(code %in% unique(c(sel_edges$i,sel_edges$j)))

# eliminate loops

if(loops == FALSE) {edges <- edges[edges$from < edges$to,]}

net<- visNetwork(nodes, 
                  edges, 
                  main = title,
height = "1000px", 
                  width = "70%")   %>%   
   visNodes(scaling =list(min =20, max=60, 
                          label=list(min=20,max=80, 
                                    maxVisible = 20)))%>%
  visEdges(scaling = list(min=20,max=60))%>%
       visOptions(highlightNearest = TRUE,
     #               selectedBy = "group", 
    #               manipulation = TRUE,
                  nodesIdSelection = TRUE) %>%
        visInteraction(navigationButtons = TRUE) %>%
         visLegend() %>%
      visIgraphLayout(layout ="layout.fruchterman.reingold",smooth = TRUE)

net
 return(net)
 } 

Application

# Load complete hypercube
hc <- readRDS("data/corpus/hc_mycorpus_states_pand.RDS")
hc <- hc %>% filter(where1 !="_no_", where2 != "_no_")

# Eliminate non foreign news 
hc<-hc[hc$where1 != substr(who,4,6),]
hc<-hc[hc$where2 != substr(who,4,6),]

# Add complete labels
map<-readRDS("data/dico_states/world_map_4326.Rdata")
labs<-st_drop_geometry(map)
labs<-labs[,c(1,4)]

# Shorten the name of USA
labs$NAME[labs$ISO3=="USA"]<-"U.S.A."


names(labs)<-c("where1","geofr1")
hc<-left_join(hc,labs)
Joining, by = "where1"
names(labs)<-c("where2","geofr2")
hc<-left_join(hc,labs)
Joining, by = "where2"
hc_geo_geo <- hc

Reference network

hc<-hc_geo_geo 



hc<-hc_filter(don = hc,
                             wgt = "news",
                             where1 = "geofr1",
                             where2 = "geofr2",
                             where1_exc = c("_no_"),
                             where2_exc = c("_no_"),
                             self = FALSE
                           )

int <- build_int(don = hc,
                 s1=2,
                 s2=2,
                 n1=1,
                 n2=1,
                 k=0)
Using 'Fij' as value column. Use 'value.var' to override
mod<-rand_int(int,
              resid = TRUE,
              diag = FALSE)


network<- geo_network(mod,
                      size = "Fij",
                      minsize = 1,
                      test = "Rchi_ij",
                      mintest = 3.84)
Joining, by = "i"
Joining, by = "j"
`summarise()` ungrouping output (override with `.groups` argument)
`summarise()` ungrouping output (override with `.groups` argument)
Joining, by = "code"
network
NA
LS0tCnRpdGxlOiAiR2VvZ3JhcGhpY2FsIGFuYWx5c2lzIG9mIG1lZGlhIgpzdWJ0aXRsZTogIkFwcGxpY2F0aW9uICIKYXV0aG9yOiAiQ2xhdWRlIEdyYXNsYW5kIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShrbml0cikKCiMjIEdsb2JhbCBvcHRpb25zCm9wdGlvbnMobWF4LnByaW50PSI3NSIpCm9wdHNfY2h1bmskc2V0KGVjaG89RkFMU0UsCgkgICAgICAgICAgICAgY2FjaGU9RkFMU0UsCiAgICAgICAgICAgICAgIHByb21wdD1GQUxTRSwKICAgICAgICAgICAgICAgdGlkeT1GQUxTRSwKICAgICAgICAgICAgICAgY29tbWVudD1OQSwKICAgICAgICAgICAgICAgbWVzc2FnZT1GQUxTRSwKICAgICAgICAgICAgICAgd2FybmluZz1GQUxTRSkKCgojIEJhc2ljIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShsdWJyaWRhdGUpCgojIEdyYXBoaWMgcGFja2FnZXMKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkodmlzTmV0d29yaykKCiMgU3BhdGlhbCBwYWNrYWdlcwpsaWJyYXJ5KHNmKQoKIyB0ZXh0IG1pbmluZyBwYWNrYWdlcwpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHF1YW50ZWRhLnRleHRwbG90cykKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShyZWFkdGV4dCkKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHJlYWRyKQpgYGAKCgoKCiMgQ29ycHVzIHByZXBhcmF0aW9uCgpXZSB1c2UgaGVyZSBkYXRhIGZyb20gTWVkaWEgY2xvdWQgd2l0aCBvbmx5IHRpdGxlIG9mIG5ld3MuIFRoZXJmb3JlIHRoZSBwcm9ncmFtIGlzIG1vcmUgc2ltcGxlIHRoYW4gaW4gY291cnNlLgoKIyMgU2VsZWN0aW9uIG9mIG1lZGlhCgoKYGBge3IgY29ycF9mciwgZXZhbD1GQUxTRX0KCiMgTG9hZCBkYXRhIHdpdGggdGhlIGZ1bmN0aW9uIGZyZWFkIChmYXN0KSBhbmQgdGhlIGVuY29kaW5nIFVURi04CmRmMTwtZnJlYWQoImRhdGEvc291cmNlL2VuX0dCUl9ndWFyZGlfMjAxOV8yMDIwLmNzdiIsIGVuY29kaW5nID0gIlVURi04IikKZGYxJG1lZGlhPC0iZW5fR0JSX2d1YXJkaSIKCgpkZjI8LWZyZWFkKCJkYXRhL3NvdXJjZS9lbl9HQlJfdGVsZWdyXzIwMTlfMjAyMC5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIpCmRmMiRtZWRpYTwtImVuX0dCUl90ZWxlZ3IiCgoKIyB0cmFuc2Zvcm0gaW4gZGF0YS50YWJsZSBmb3JtYXQKZGY8LXJiaW5kKGRmMSxkZjIpCnJtKGRmMSxkZjIpCgoKIyBzZWxlY3QgY29sdW1uIG9mIGludGVyZXN0CmRmJGlkIDwtIGRmJHN0b3JpZXNfaWQKZGYkd2hvIDwtIGRmJG1lZGlhCmRmJHdoZW4gPC0gYXMuRGF0ZShkZiRwdWJsaXNoX2RhdGUpCmRmJHRleHQgPC0gZGYkdGl0bGUKZGY8LWRmWyxjKCJpZCIsIndobyIsIndoZW4iLCJ0ZXh0IildCmRmPC1kZltvcmRlcih3aGVuKSxdCgojIHNlbGVjdCBwZXJpb2Qgb2YgaW50ZXJlc3QKbWludGltZTwtYXMuRGF0ZSgiMjAxOS0wMS0wMSIpCm1heHRpbWU8LWFzLkRhdGUoIjIwMjAtMTItMzEiKQpkZjwtZGZbKGlzLm5hKGRmJHdoZW4pPT1GKSxdIApkZjwtZGZbYXMuRGF0ZShkZiR3aGVuKSA+PSBtaW50aW1lLF0KZGY8LWRmW2FzLkRhdGUoZGYkd2hlbikgPD0gbWF4dGltZSxdCgojIGVsaW1pbmF0ZSBkdXBsaWNhdGUKZGY8LWRmW2R1cGxpY2F0ZWQoZGYkdGV4dCk9PUYsXQoKCgpgYGAKCgojIyBDaGVjayBvZiB0aW1lIGZyZXF1ZW5jeQoKCiMjIyBUaW1lIGRpdmlzaW9ucwoKV2UgdHJhbnNmb3JtIHRoZSBwcmV2aW91cyBkYXRhLmZyYW1lIGluIGEgZGF0YS50YWJsZSBmb3JtYXQgZm9yIGVhc2llciBvcGVyYXRpb25zIG9mIGFnZ3JlZ2F0aW9uIAoKYGBge3IgdGltZSBkaXYsIGV2YWwgPSBGQUxTRX0KZHQ8LWFzLmRhdGEudGFibGUoZGYpCmR0JGRheSAgICAgPC0gYXMuRGF0ZShkdCR3aGVuKQpkdCR3ZWVrICAgIDwtIGN1dChkdCRkYXksICJ3ZWVrcyIsIHN0YXJ0Lm9uLm1vbmRheT1UUlVFKQpkdCRtb250aCAgIDwtIGN1dChkdCRkYXksICJtb250aHMiKQpkdCR3ZWVrZGF5IDwtIHdlZWtkYXlzKGR0JGRheSkKCiMgU2F2ZSBkYXRhIGZyYW1lCnNhdmVSRFMoZHQsImRhdGEvY29ycHVzL2R0X215Y29ycHVzLlJEUyIpIApgYGAKCgojIyMgTmV3cyBieSB3ZWVrCgoKCgpXZSBleGFtaW5lIGlmIHRoZSBkaXN0cmlidXRpb24gaXMgcmVndWxhciBieSB3ZWVrIGZvciB0aGUgZGlmZmVyZW50IG1lZGlhIG9mIHRoZSBjb3JwdXMuCgpgYGB7ciBuZXdzX3dlZWtfZnJ9CmR0PC1yZWFkUkRTKCJkYXRhL2NvcnB1cy9kdF9teWNvcnB1cy5SRFMiKQpuZXdzX3dlZWtzPC1kdFssLihuZXdzdG90PS5OKSxieT0uKHdlZWssd2hvKV0KCnA8LWdncGxvdChuZXdzX3dlZWtzLCBhZXMoeD1hcy5EYXRlKHdlZWspLHk9bmV3c3RvdCwgY29sPXdobykpKwogICBnZW9tX2xpbmUoKSsKICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xvZXNzJywgZm9ybXVsYSA9ICd5fngnKSsKICAgc2NhbGVfeV9jb250aW51b3VzKCJOdW1iZXIgb2YgbmV3cyIsIGxpbWl0cyA9IGMoMCxOQSkpICsKICAgc2NhbGVfeF9kYXRlKCJXZWVrIChzdGFydGluZyBvbiBtb25kYXkpIikgKwogICAgICAgICBnZ3RpdGxlKGxhYmVsID0iQ29ycHVzIDogZGlzdHJpYnV0aW9uIG9mIG5ld3MgYnkgd2VlayIpCnAKYGBgCgojIyMgTmV3cyBieSB3ZWVrZGF5CgpXZSBleGFtaW5lIGlmIHRoZSBkaXN0cmlidXRpb24gaXMgcmVndWxhciBieSB3ZWVrZGF5IGFuZCBjaGVjayBpbiBwYXJ0aWN1bGFyIHRoZSBlZmZlY3Qgb2YgdGhlIHdlZWstZW5kLgoKYGBge3IgbmV3c193ZWVrZGF5c19mcn0KI2NvbXB1dGUgZnJlcXVlbmNpZXMgYnkgd2Vla2RheQpuZXdzX3dlZWtkYXlzPC1kdFssLihuZXdzdG90PS5OKSxieT0uKHdlZWtkYXksd2hvKV0KbmV3c193ZWVrZGF5czwtbmV3c193ZWVrZGF5c1ssLih3ZWVrZGF5LG5ld3NwY3Q9MTAwKm5ld3N0b3Qvc3VtKG5ld3N0b3QpKSxieT0uKHdobyldCgoKIyBUcmFuc2xhdGUgd2Vla2RheXMgaW4gZW5nbGlzaCBhbmQgb3JkZXIKbmV3c193ZWVrZGF5cyR3ZWVrZGF5PC1hcy5mYWN0b3IobmV3c193ZWVrZGF5cyR3ZWVrZGF5KQpsZXZlbHMobmV3c193ZWVrZGF5cyR3ZWVrZGF5KTwtYygiNy5TdW5kYXkiLCI0LldlZG5lc2RheSIsIjEuTW9uZGF5IiwiMi5UdWVzZGF5IiwiMy5UaHVyc2RheSIsIjYuU2F0aHVyZGF5IiwiNS5GcmlkYXkiKQpuZXdzX3dlZWtkYXlzJHdlZWtkYXk8LWFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIobmV3c193ZWVrZGF5cyR3ZWVrZGF5KSkKbmV3c193ZWVrZGF5czwtbmV3c193ZWVrZGF5c1tvcmRlcihuZXdzX3dlZWtkYXlzJHdlZWtkYXkpLF0KCgpwPC1nZ3Bsb3QobmV3c193ZWVrZGF5cywgYWVzKHg9d2Vla2RheSxmaWxsID0gd2hvLCB5PW5ld3NwY3QpKSsKICAgICAgICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpKwogICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoIlNoYXJlIG9mIG5ld3MgKCUpIiwgbGltaXRzID0gYygwLE5BKSkgKwogICAgICAgICBnZ3RpdGxlKGxhYmVsID0iQ29ycHVzIDogZGlzdHJpYnV0aW9uIG9mIG5ld3MgYnkgd2VlayBkYXkiKQpwCmBgYAoKCiMjIFRyYW5zZm9ybSBpbiBxdWFudGVkYSBjb3JwdXMKCgoKYGBge3Igc2VudF9mcn0KCiMgdHJhbnNmb3JtIGluIHF1YW50ZWRhCnFkPC1jb3JwdXMoZHQsZG9jaWRfZmllbGQgPSAiaWQiLHRleHRfZmllbGQgPSAidGV4dCIpCgoKIyBmaWx0ZXIgYnkgbnVtYmVyIG9mIHRva2VucyBieSBzZW50ZW5jZQpxZCRuYnQ8LW50b2tlbih0ZXh0cyhxZCkpCnFkPC1jb3JwdXNfc3Vic2V0KHFkLCBuYnQ8MTAwKQpxZDwtY29ycHVzX3N1YnNldChxZCwgbmJ0PjIpCgoKCiMgU2F2ZSBjb3JwdXMgaW4gcWQgZm9ybWF0CnNhdmVSRFMocWQsImRhdGEvY29ycHVzL3FkX215Y29ycHVzLlJEUyIpCgoKCmhlYWQocWQpCnN1bW1hcnkocWQsMykKYGBgCgoKCgoKIyBHZW9ncmFwaGljYWwgdGFncwoKCiMjIFByZXBhcmF0aW9uIG9mIGRhdGEKCgojIyMgTG9hZCBkaWN0b25hcnkKCldlIHN0YXJ0IGJ5IGxvYWRpbmcgdGhlIGxhc3QgdmVyc2lvbiBvZiB0aGUgSW1hZ2V1biBkaWN0aW9uYXJ5IGFuZCB3ZSBleHRyYWN0IG91ciB0YXJnZXQgbGFuZ3VhZ2UgKGhlcmUgOiBlbmdsaXNoKS4KCmBgYHtyIGxvYWRfZGljdH0KIyBMb2FkIG11bHRpbGFuZ3VhZ2UgZGljdGlvbmFyeQpkaWN0PC1mcmVhZCgiZGF0YS9kaWNvX3N0YXRlcy9nbG9iYWxfc3RhdGVfVjIuY3N2IikKCiMgU2VsZWN0IGZyZW5jaCBkaWN0aW9uYXJ5CmRpY3QgPC0gZGljdFtkaWN0JGxhbmc9PSJlbiIsXQoKCmhlYWQoZGljdCkKYGBgCgojIyMgTG9hZCBjb3JwdXMKCmBgYHtyfQpxZCA8LSByZWFkUkRTKCJkYXRhL2NvcnB1cy9xZF9teWNvcnB1cy5SRFMiKQpgYGAKCiMjIyBMb2FkIHRhZ2dpbmcgZnVuY3Rpb24KCmBgYHtyIGZ1bmNfYW5ub3RhdGV9CmV4dHJhY3RfdGFncyA8LSBmdW5jdGlvbihxZCA9IHFkLCAgICAgICAgICAgICAgICAgICAgICAjIHRoZSBjb3JwdXMgb2YgaW50ZXJlc3QKICAgICAgICAgICAgICAgICAgICAgICAgIGxhbmcgPSAiZnIiLCAgICAgICAgICAgICAgICAgICMgdGhlIGxhbmd1YWdlIHRvIGJlIHVzZWQKICAgICAgICAgICAgICAgICAgICAgICAgIGRpY3QgPSBkaWN0LCAgICAgICAgICAgICAgICAgICMgdGhlIGRpY3Rpb25hcnkgb2YgdGFyZ2V0IAogICAgICAgICAgICAgICAgICAgICAgICAgY29kZSA9ICJJU08zIiAsICAgICAgICAgICAgICAgICMgdmFyaWFibGUgdXNlZCBmb3IgY29kaW5nCiAgICAgICAgICAgICAgICAgICAgICAgICBhbGlhcyA9ICJ4IiwgICAgICAgICAgICAgICAgICAgIyB2YXJpYWJsZSB1c2VkIGZvciBhbGlhcwogICAgICAgICAgICAgICAgICAgICAgICAgdGFnc25hbWUgPSAic3RhdGVzIiwgICAgICAgICAgICMgbmFtZSBvZiB0aGUgdGFncyBjb2x1bW4KICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ICA9IGMoIiciLCLigJkiLCItIiksICAgICAgICMgc3BsaXQgbGlzdAogICAgICAgICAgICAgICAgICAgICAgICAgdG9sb3cgPSBUUlVFICAsICAgICAgICAgICAgICAgICMgVG9rZW5pemUgdGV4dAogICAgICAgICAgICAgICAgICAgICAgICAgY29tcHMgPSBjKCJBZnJpcXVlIGR1IHN1ZCIpICAjIGNvbXBvdW5kcwogICAgICAgICAgICAgICAgICAgICAgICAgKQp7IAoKCiAgCiMgVG9rZW5pemUgIAp4PC1hcy5jaGFyYWN0ZXIocWQpCgoKaWYobGVuZ3RoKHNwbGl0KSA+IDApIHsgcmVnPC1wYXN0ZShzcGxpdCwgY29sbGFwc2UgPSAnfCcpCiAgICAgICAgICAgICAgICAgICAgICAgeCA8LSBnc3ViKHJlZywiICIseCl9ICAKaWYodG9sb3cpIHsgeCA8LSB0b2xvd2VyKHgpfSAKdG9rczwtdG9rZW5zKHgpCgojIGNvbXBvdW5kcwppZihsZW5ndGgoc3BsaXQpID4gMCkgeyByZWc8LXBhc3RlKHNwbGl0LCBjb2xsYXBzZSA9ICd8JykKICAgICAgICAgICAgICAgICAgICAgICBjb21wczwtIGdzdWIocmVnLCIgIixjb21wcyl9ICAKaWYodG9sb3cpICAgICAgIHtjb21wcyA8LSB0b2xvd2VyKGNvbXBzKX0gIAp0b2tzPC10b2tlbnNfY29tcG91bmQodG9rcyxwYXR0ZXJuPXBocmFzZShjb21wcykpCgogIAojIExvYWQgZGljdGlvbmFyaWVzIGFuZCBjcmVhdGUgY29tcG91bmRzCgogICMjIFRhcmdldCBkaWN0aW9uYXJ5CgpsYWJlbHMgPC1kaWN0W1thbGlhc11dCmlmKGxlbmd0aChzcGxpdCkgPiAwKSB7IHJlZzwtcGFzdGUoc3BsaXQsIGNvbGxhcHNlID0gJ3wnKQogICAgICAgICAgICAgICAgICAgICAgIGxhYmVsczwtIGdzdWIocmVnLCIgIixsYWJlbHMpfSAgCmlmKHRvbG93KSAgICAgICB7bGFiZWxzIDwtIHRvbG93ZXIobGFiZWxzKX0gIAp0b2tzPC10b2tlbnNfY29tcG91bmQodG9rcyxwYXR0ZXJuPXBocmFzZShsYWJlbHMpKQogIAogIyBjcmVhdGUgcXVhbnRlZGEgZGljdGlvbmFyeQprZXlzIDwtZ3N1YigiICIsIl8iLGxhYmVscykKcWRfZGljdDwtYXMubGlzdChrZXlzKQpuYW1lcyhxZF9kaWN0KTwtZGljdFtbY29kZV1dCnFkX2RpY3Q8LWRpY3Rpb25hcnkocWRfZGljdCx0b2xvd2VyID0gRkFMU0UpCgojIElkZW50aWZ5IGdlbyB0YWdzIChzdGF0ZXMgb3IgcmVnIG9yIG9yZyAuLi4pCnRva3NfdGFncyA8LSB0b2tlbnNfbG9va3VwKHRva3MsIHFkX2RpY3QsIGNhc2VfaW5zZW5zaXRpdmUgPSBGKQp0b2tzX3RhZ3MgPC0gbGFwcGx5KHRva3NfdGFncywgdW5pcXVlKQp0b2tzX3RhZ3M8LWFzLnRva2Vucyh0b2tzX3RhZ3MpCmxpc3RfdGFnczwtZnVuY3Rpb24oeCl7cmVzPC1wYXN0ZSh4LCBjb2xsYXBzZT0nICcpfQpkb2N2YXJzKHFkKVtbdGFnc25hbWVdXTwtYXMuY2hhcmFjdGVyKGxhcHBseSh0b2tzX3RhZ3MsRlVOPWxpc3RfdGFncykpCmRvY3ZhcnMocWQpW1twYXN0ZSgibmIiLHRhZ3NuYW1lLHNlcD0iIildXTwtbnRva2VuKHRva3NfdGFncykKCgoKIyBFeHBvcnQgcmVzdWx0cwpyZXR1cm4ocWQpCiB9CmBgYAoKCgojIyBHZW9ncmFwaGljYWwgYW5ub3RhdGlvbgoKIyMjIEFubm90YXRlIGFsbCBlbnRpdGllcwoKCmBgYHtyIGFubm90YXRlLCBldmFsPUZBTFNFfQoKCgp0MTwtU3lzLnRpbWUoKQoKZnJjb21wczwtYygiQ2hpbmEgc2VhIiwgImluZGlhbiBvY2VhbiIsICJhbWVyaWNhbiBjb250aW5lbnQiLCAiYW1lcmljYW4gY291bnRyaWVzIikKCnFkIDwtIGV4dHJhY3RfdGFncyAocWQgPSBxZCwKICAgICAgICAgICAgICAgICAgICAgbGFuZz0iZW4iLAogICAgICAgICAgICAgICAgICAgICBkaWN0ID0gZGljdCwKICAgICAgICAgICAgICAgICAgICAgY29kZSA9ICJJU08zIiwKICAgICAgICAgICAgICAgICAgICAgYWxpYXMgPSAieCIsCiAgICAgICAgICAgICAgICAgICAgIHRhZ3NuYW1lID0gInN0YXRlcyIsCiAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gYygiJyIsIuKAmSIsIi0iKSwKICAgICAgICAgICAgICAgICAgICAgY29tcHMgPSBmcmNvbXBzLAogICAgICAgICAgICAgICAgICAgICB0b2xvdyA9IFRSVUUpCgp0MiA9IFN5cy50aW1lKCkKcGFzdGUoIlByb2dyYW0gZXhlY3V0ZWQgaW4gIiwgdDItdDEpCgp0YWJsZShxZCRuYnN0YXRlcykKCgpgYGAKCgoKIyMjIGNoZWNrIG5ld3Mgd2l0aCBtYXhpbXVtIHN0YXRlIG51bWJlcgoKYGBge3IgY2hlY2tfc3RhdGVzX25ld3N9CnRhYmxlKHFkJG5ic3RhdGVzKQpjaGVjazwtY29ycHVzX3N1YnNldChxZCxuYnN0YXRlcz4zKQp4PC1kYXRhLmZyYW1lKHdobz1jaGVjayR3aG8sd2hlbiA9IGNoZWNrJHdoZW4sdGV4dD1hcy5jaGFyYWN0ZXIoY2hlY2spLHN0YXRlcz1jaGVjayRzdGF0ZXMsbmJzdGF0ZXM9Y2hlY2skbmJzdGF0ZXMpCng8LXhbb3JkZXIoeCRuYnN0YXRlcyxkZWNyZWFzaW5nID0gVCksXQprYWJsZSh4KQpgYGAKCgoKCgoKCiMjIyBTYXZlIGdlb2dyYXBoaWNhbGx5IGFub3RhdGVkIGNvcnB1cwoKYGBge3J9CnNhdmVSRFMocWQsImRhdGEvY29ycHVzL3FkX215Y29ycHVzX3N0YXRlcy5SRFMiKQpwYXN0ZSgiU2l6ZSBvZiByZXN1bHRpbmcgZmlsZSA9ICIscm91bmQoZmlsZS5zaXplKCJkYXRhL2NvcnB1cy9xZF9teWNvcnB1c19zdGF0ZXMuUkRTIikvMTAwMDAwMCwzKSwgIk1vIikKYGBgCgoKCgojIFRoZW1hdGljIHRhZ3MKCgoKYGBge3J9CnFkIDwtcmVhZFJEUygiZGF0YS9jb3JwdXMvcWRfbXljb3JwdXNfc3RhdGVzLlJEUyIpCmBgYAoKCgojIyBMb2FkIHRhZ2dpbmcgZnVuY3Rpb24KCgpgYGB7ciBmdW5jX2Fubm90YXRlMn0KZXh0cmFjdF90YWdzIDwtIGZ1bmN0aW9uKHFkID0gcWQsICAgICAgICAgICAgICAgICAgICAgICMgdGhlIGNvcnB1cyBvZiBpbnRlcmVzdAogICAgICAgICAgICAgICAgICAgICAgICAgbGFuZyA9ICJmciIsICAgICAgICAgICAgICAgICAgIyB0aGUgbGFuZ3VhZ2UgdG8gYmUgdXNlZAogICAgICAgICAgICAgICAgICAgICAgICAgZGljdCA9IGRpY3QsICAgICAgICAgICAgICAgICAgIyB0aGUgZGljdGlvbmFyeSBvZiB0YXJnZXQgCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2RlID0gImNvZGUiICwgICAgICAgICAgICAgICAgIyB2YXJpYWJsZSB1c2VkIGZvciBjb2RpbmcKICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWFzID0gImFsaWFzIiwgICAgICAgICAgICAgICAjIHZhcmlhYmxlIHVzZWQgZm9yIGFsaWFzCiAgICAgICAgICAgICAgICAgICAgICAgICB0YWdzbmFtZSA9ICJzdGF0ZXMiLCAgICAgICAgICAgIyBuYW1lIG9mIHRoZSB0YWdzIGNvbHVtbgogICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXQgID0gYygiJyIsIuKAmSIsIi0iKSwgICAgICAgIyBzcGxpdCBsaXN0CiAgICAgICAgICAgICAgICAgICAgICAgICB0b2xvdyA9IFRSVUUgICwgICAgICAgICAgICAgICAgIyBUb2tlbml6ZSB0ZXh0CiAgICAgICAgICAgICAgICAgICAgICAgICBjb21wcyA9IGMoIkFmcmlxdWUgZHUgc3VkIikgICMgY29tcG91bmRzCiAgICAgICAgICAgICAgICAgICAgICAgICApCnsgCgoKICAKIyBUb2tlbml6ZSAgCng8LWFzLmNoYXJhY3RlcihxZCkKCgppZihsZW5ndGgoc3BsaXQpID4gMCkgeyByZWc8LXBhc3RlKHNwbGl0LCBjb2xsYXBzZSA9ICd8JykKICAgICAgICAgICAgICAgICAgICAgICB4IDwtIGdzdWIocmVnLCIgIix4KX0gIAppZih0b2xvdykgeyB4IDwtIHRvbG93ZXIoeCl9IAp0b2tzPC10b2tlbnMoeCkKCiMgY29tcG91bmRzCmlmKGxlbmd0aChzcGxpdCkgPiAwKSB7IHJlZzwtcGFzdGUoc3BsaXQsIGNvbGxhcHNlID0gJ3wnKQogICAgICAgICAgICAgICAgICAgICAgIGNvbXBzPC0gZ3N1YihyZWcsIiAiLGNvbXBzKX0gIAppZih0b2xvdykgICAgICAge2NvbXBzIDwtIHRvbG93ZXIoY29tcHMpfSAgCnRva3M8LXRva2Vuc19jb21wb3VuZCh0b2tzLHBhdHRlcm49cGhyYXNlKGNvbXBzKSkKCiAgCiMgTG9hZCBkaWN0aW9uYXJpZXMgYW5kIGNyZWF0ZSBjb21wb3VuZHMKCiAgIyMgVGFyZ2V0IGRpY3Rpb25hcnkKCmxhYmVscyA8LWRpY3RbW2FsaWFzXV0KaWYobGVuZ3RoKHNwbGl0KSA+IDApIHsgcmVnPC1wYXN0ZShzcGxpdCwgY29sbGFwc2UgPSAnfCcpCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPC0gZ3N1YihyZWcsIiAiLGxhYmVscyl9ICAKaWYodG9sb3cpICAgICAgIHtsYWJlbHMgPC0gdG9sb3dlcihsYWJlbHMpfSAgCnRva3M8LXRva2Vuc19jb21wb3VuZCh0b2tzLHBhdHRlcm49cGhyYXNlKGxhYmVscykpCiAgCiAjIGNyZWF0ZSBxdWFudGVkYSBkaWN0aW9uYXJ5CmtleXMgPC1nc3ViKCIgIiwiXyIsbGFiZWxzKQpxZF9kaWN0PC1hcy5saXN0KGtleXMpCm5hbWVzKHFkX2RpY3QpPC1kaWN0W1tjb2RlXV0KcWRfZGljdDwtZGljdGlvbmFyeShxZF9kaWN0LHRvbG93ZXIgPSBGQUxTRSkKCiMgSWRlbnRpZnkgZ2VvIHRhZ3MgKHN0YXRlcyBvciByZWcgb3Igb3JnIC4uLikKdG9rc190YWdzIDwtIHRva2Vuc19sb29rdXAodG9rcywgcWRfZGljdCwgY2FzZV9pbnNlbnNpdGl2ZSA9IEYpCnRva3NfdGFncyA8LSBsYXBwbHkodG9rc190YWdzLCB1bmlxdWUpCnRva3NfdGFnczwtYXMudG9rZW5zKHRva3NfdGFncykKbGlzdF90YWdzPC1mdW5jdGlvbih4KXtyZXM8LXBhc3RlKHgsIGNvbGxhcHNlPScgJyl9CmRvY3ZhcnMocWQpW1t0YWdzbmFtZV1dPC1hcy5jaGFyYWN0ZXIobGFwcGx5KHRva3NfdGFncyxGVU49bGlzdF90YWdzKSkKZG9jdmFycyhxZClbW3Bhc3RlKCJuYiIsdGFnc25hbWUsc2VwPSIiKV1dPC1udG9rZW4odG9rc190YWdzKQoKCgojIEV4cG9ydCByZXN1bHRzCnJldHVybihxZCkKIH0KYGBgCgoKIyMgVGhlIHBhbmRlbWljIHRvcGljCgojIyMgRGljdGlvbmFyeQoKV2UgZGVjaWRlIGhlcmUgdG8gdXNlIGxvd2VyIGNhc2UgdHJhbnNmb3JtYXRpb24uIFdlIHVzZSBhIHN0YXIgZm9yIHRoZSB3b3JkcyB0aGF0IGNhbiB0YWtlIGEgcGx1cmFsIGZvcm0uIFdlIGhhZCBvZiBjb3Vyc2UgY292aWQgYW5kIGNvcm9uYXZpcnVzIAoKYGBge3IgZGljbyBwYW5kZW1pY30KbGFiZWwgPC0gYygicGFuZGVtaWMqIiwgImVwaWRlbWljKiIsICJ2aXJ1cyIsICJ3b3JsZCBoZWFsdGggb3JnYW5pc2F0aW9uIiwgImVib2xhIiwgICJoMW4xIiwic3JhcyIsICJjaGlrdW5ndW55YSIsICJjaG9sZXJhIiwgImZsdSIsICJjb3ZpZCoiLCJjb3JvbmF2aXJ1cyIpCmNvZGUgIDwtIHJlcCgicGFuZCIsIGxlbmd0aChsYWJlbCkpCmxhbmcgIDwtIHJlcCgiZW4iLCBsZW5ndGgobGFiZWwpKQpkaWN0X3BhbmRlIDwtIGRhdGEuZnJhbWUoY29kZSxsYW5nLGxhYmVsKQprYWJsZShkaWN0X3BhbmRlKQoKZW5jb21wczwtYygiY29tcHV0ZXIgdmlydXMiKQpgYGAKCgojIyMgQW5ub3RhdGlvbgoKYGBge3IgYW5ub3RhdGUgcGFuZGVtaWMsIGV2YWw9RkFMU0V9CgpxZCA8LSBleHRyYWN0X3RhZ3MgKHFkID0gcWQsCiAgICAgICAgICAgICAgICAgICAgIGxhbmc9ImVuIiwKICAgICAgICAgICAgICAgICAgICAgZGljdCA9IGRpY3RfcGFuZGUsCiAgICAgICAgICAgICAgICAgICAgIGNvZGUgPSAiY29kZSIsCiAgICAgICAgICAgICAgICAgICAgIGFsaWFzID0gImxhYmVsIiwKICAgICAgICAgICAgICAgICAgICB0YWdzbmFtZSA9ICJwYW5kIiwKICAgICAgICAgICAgICAgICAgICAgc3BsaXQgPSBjKCInIiwi4oCZIiwiLSIpLAogICAgICAgICAgICAgICAgICAgICBjb21wcyA9IGVuY29tcHMsCiAgICAgICAgICAgICAgICAgICAgIHRvbG93ID0gVFJVRSkKCnRhYmxlKHFkJG5icGFuZCkKCnNhdmVSRFMocWQsImRhdGEvY29ycHVzL3FkX215Y29ycHVzX3N0YXRlc190b3BpYy5SRFMiKQoKYGBgCgoKIyMjIFZpc3VhbGl6YXRpb24KCgpgYGB7ciB2aXN1YWxpemF0aW9uIHBhbmRlbWljfQp4PC1kYXRhLnRhYmxlKGRvY3ZhcnMocWQpKQp4JHRhZzwteCRuYnBhbmQgIT0wCnRhYjwteFssLih0b3Q9Lk4pLGJ5PS4obW9udGgsdGFnLCB3aG8pXQp0YWI8LXRhYlt0YWIkdGFnPT1UUlVFLF0KdGFiJG1vbnRoPC1hcy5EYXRlKHRhYiRtb250aCkKCiAgICAgICAKICAgICAgIHA8LWdncGxvdCh0YWIsIGFlcyh4PW1vbnRoLGZpbGwgPXdobywgeT10b3QpKSsKICAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSsKICAgICAgICAgZ2d0aXRsZShsYWJlbCA9IlBhbmRlbWljIDogZGlzdHJpYnV0aW9uIG9mIHRhZ3MgYnkgbW9udGggYW5kIG1lZGlhIikKcApgYGAKCgoKCiMgSHlwZXJjdWJlcyBjcmVhdGlvbgoKCgojIyBBZ2dyZWdhdGlvbiBmdW5jdGlvbgoKCmBgYHtyfQoKIycgQHRpdGxlIGNyZWF0ZSBhbiBoeXBlcmN1YmUKIycgQG5hbWUgaHlwZXJjdWJlCiMnIEBkZXNjcmlwdGlvbiBjcmVhdGUgYSBuZXR3b3JrIG9mIGludGVybGlua2VkIHN0YXRlcwojJyBAcGFyYW0gY29ycHVzIGEgY29ycHVzIG9mIG5ld3MgaW4gcXVhbnRlZGEgZm9ybWF0CiMnIEBwYXJhbSBvcmRlciBhbiBvcmRlciBvZiBzZW50ZW5jZXMgaW4gdGhlIG5ld3MKIycgQHBhcmFtIHdobyB0aGUgc291cmNlIGRpbWVuc2lvbgojJyBAcGFyYW0gd2hlbiB0aGUgdGltZSBkaW1lbnNpb24KIycgQHBhcmFtIHRpbWVzcGFuIGFnZ3JlYXRpb24gb2YgdGltZQojJyBAcGFyYW0gd2hhdCBhIGxpc3Qgb2YgdG9waWNzCiMnIEBwYXJhbSB3aGVyZTEgYSBsaXN0IG9mIHN0YXRlcwojJyBAcGFyYW0gd2hlcmUyICBhIGxpc3Qgb2Ygc3RhdGVzCgoKaHlwZXJjdWJlICAgPC0gZnVuY3Rpb24oIGNvcnB1cyA9IHFkLAogICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9ICJvcmRlciIsCiAgICAgICAgICAgICAgICAgICAgICAgIHdobyA9ICJzb3VyY2UiLAogICAgICAgICAgICAgICAgICAgICAgICB3aGVuID0gIndoZW4iLAogICAgICAgICAgICAgICAgICAgICAgICB0aW1lc3BhbiA9ICJ3ZWVrIiwKICAgICAgICAgICAgICAgICAgICAgICAgd2hhdCA9ICJ3aGF0IiwKICAgICAgICAgICAgICAgICAgICAgICAgd2hlcmUxID0gIndoZXJlMSIsCiAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMiA9ICJ3aGVyZTIiKQp7CgoKICAKIyBwcmVwYXJlIGRhdGEKCiAgZG9uPC1kb2N2YXJzKGNvcnB1cykKICAKICBkZjwtZGF0YS50YWJsZShpZCAgICAgPSBkb2NpZChjb3JwdXMpLAogICAgICAgICAgICAgICAgIG9yZGVyICA9IGRvbltbb3JkZXJdXSwKICAgICAgICAgICAgICAgICB3aG8gICAgPSBkb25bW3dob11dLAogICAgICAgICAgICAgICAgIHdoZW4gICA9IGRvbltbd2hlbl1dLAogICAgICAgICAgICAgICAgIHdoYXQgICA9IGRvbltbd2hhdF1dLAogICAgICAgICAgICAgICAgIHdoZXJlMSA9IGRvbltbd2hlcmUxXV0sCiAgICAgICAgICAgICAgICAgd2hlcmUyID0gZG9uW1t3aGVyZTJdXSkKCiAgIyBhZGp1c3QgaWQKIGRmJGlkPC1wYXN0ZShkZiRpZCwiXyIsZGYkb3JkZXIsc2VwPSIiKQogCiMgY2hhbmdlIHRpbWUgc3BhbgogIGRmJHdoZW48LWFzLmNoYXJhY3RlcihjdXQoYXMuRGF0ZShkZiR3aGVuKSwgdGltZXNwYW4sIHN0YXJ0Lm9uLm1vbmRheSA9IFRSVUUpKQoKIyB1bm5lc3Qgd2hlcmUxCiAgZGYkd2hlcmUxW2RmJHdoZXJlMT09IiJdPC0iX25vXyIKICBkZjwtdW5uZXN0X3Rva2VucyhkZix3aGVyZTEsd2hlcmUxLHRvX2xvd2VyPUYpCiAgCiMgdW5uZXN0IHdoZXJlMgogIGRmJHdoZXJlMltkZiR3aGVyZTI9PSIiXTwtIl9ub18iCiAgZGY8LXVubmVzdF90b2tlbnMoZGYsd2hlcmUyLHdoZXJlMix0b19sb3dlcj1GKSAKICAKIyB1bm5lc3Qgd2hhdAogIGRmJHdoYXRbZGYkd2hhdD09IiJdPC0iX25vXyIKICBkZjwtdW5uZXN0X3Rva2VucyhkZix3aGF0LHdoYXQsdG9fbG93ZXI9RikgCiAgCgoKIyBDb21wdXRlIHdlaWdodCBvZiBuZXdzCiAgbmV3c3dndDwtZGZbLGxpc3Qod2d0PTEvLk4pLGxpc3QoaWQpXQogIGRmIDwtIG1lcmdlKGRmLG5ld3N3Z3QsIGJ5PSJpZCIpCgoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gSHlwZXJjdWJlIGNyZWF0aW9uIC0tLS0tLS0tLS0tLS0tLS0tLS0tIwogIAogIAojIEFnZ3JlZ2F0ZQogIGhjPC0gZGZbLC4odGFncyA9IC5OLCBuZXdzPXN1bSh3Z3QpKSAsLihvcmRlcix3aG8sIHdoZW4sd2hlcmUxLHdoZXJlMiwgd2hhdCldCiAgCiMgQ29udmVydCBkYXRlIHRvIHRpbWUKICBoYyR3aGVuPC1hcy5EYXRlKGhjJHdoZW4pCiAgCiMgZXhwb3J0CiAgcmV0dXJuKGhjKQogIAp9CgpgYGAKCgojIyBQYW5kZW1pYyBoeXBlcmN1YmUKCmBgYHtyfQpxZDwtcmVhZFJEUygiZGF0YS9jb3JwdXMvcWRfbXljb3JwdXNfc3RhdGVzX3RvcGljLlJEUyIpCnFkJG9yZGVyPC0xCgpoYzwtaHlwZXJjdWJlKCBjb3JwdXMgICA9IHFkLAogICAgICAgICAgICAgICAgICAgIG9yZGVyICAgID0gIm9yZGVyIiwKICAgICAgICAgICAgICAgICAgICB3aG8gICAgICA9ICJ3aG8iLAogICAgICAgICAgICAgICAgICAgIHdoZW4gICAgID0gIndoZW4iLAogICAgICAgICAgICAgICAgICAgIHRpbWVzcGFuID0gImRheSIsCiAgICAgICAgICAgICAgICAgICAgd2hhdCAgICAgPSAicGFuZCIsCiAgICAgICAgICAgICAgICAgICAgd2hlcmUxICAgPSAic3RhdGVzIiwKICAgICAgICAgICAgICAgICAgICB3aGVyZTIgICA9ICJzdGF0ZXMiKQoKc2F2ZVJEUyhoYywiZGF0YS9jb3JwdXMvaGNfbXljb3JwdXNfc3RhdGVzX3BhbmQuUkRTIikKcGFzdGUoIlNpemUgb2YgcmVzdWx0aW5nIGZpbGUgPSAiLHJvdW5kKGZpbGUuc2l6ZSgiZGF0YS9jb3JwdXMvaGNfbXljb3JwdXNfc3RhdGVzX3BhbmQuUkRTIikvMTAwMDAwMCwzKSwgIk1vIikKYGBgCgoKCgoKIyBIeXBlcmN1YmVzIGV4cGxvcmF0aW9uCgoKCgoKYGBge3J9CiMjIyMgLS0tLS0tLS0tLS0tLS0tLSB0ZXN0Y2hpMiAtLS0tLS0tLS0tLS0tLS0tCiMnIEB0aXRsZSAgQ29tcHV0ZSB0aGUgYXZlcmFnZSBzYWxpZW5jZSBvZiB0aGUgdG9waWMgYW5kIHRlc3Qgc2lnbmlmaWNhbmNlIG9mIGRldmlhdGlvbgojJyBAbmFtZSB3aGF0CiMnIEBkZXNjcmlwdGlvbiBjcmVhdGUgYSB0YWJsZSBhbmQgZ3JhcGhpYyBvZiB0aGUgdG9waWMKIycgQHBhcmFtIHRhYnRlc3QgYSB0YWJsZSB3aXRoIHZhcmlhYmxlIHRyaWFsLCBzdWNjZXNzIGFuZCBudWxsLnZhbHVlCiMnIEBwYXJhbSBtaW5zYW1wIDogVGhyZXNob2xkIG9mIHNhbXBsZSBzaXplIHJlcXVlc3RlZCBmb3Igc2FsaWVuY2UgY29tcHV0YXRpb24KIycgQHBhcmFtIG1pbnRlc3QgOiBUaHJlc2hvbGQgb2YgZXN0aW1hdGVkIHZhbHVlIHJlcXVlc3RlZCBmb3IgY2hpLXNxdWFyZSB0ZXN0CgoKdGVzdGNoaTI8LWZ1bmN0aW9uKHRhYnRlc3Q9dGFidGVzdCwKICAgICAgICAgICAgICAgICAgIG1pbnNhbXAgPSAyMCwKICAgICAgICAgICAgICAgICAgIG1pbnRlc3QgPSA1KSAKewogIHRhYjwtdGFidGVzdAogIG48LWRpbSh0YWIpWzFdCiAgCiAgIyBDb21wdXRlIHNhbGllbmNlIGlmIHNhbXBsZSBzaXplIHN1ZmZpY2llbnQgKGRlZmF1bHQgOiBOPjIwKQogIHRhYiRlc3RpbWF0ZSA8LU5BCiAgdGFiJHNhbGllbmNlIDwtTkEKICB0YWIkY2hpMjwtTkEKICB0YWIkcC52YWx1ZTwtTkEKICBpZiAodGFiJHRyaWFsID4gbWluc2FtcCl7IHRhYiRlc3RpbWF0ZTwtcm91bmQodGFiJHN1Y2Nlc3MvdGFiJHRyaWFsLDUpCiAgdGFiJHNhbGllbmNlPC10YWIkZXN0aW1hdGUvdGFiJG51bGwudmFsdWUKICAKICAjIENoaS1zcXVhcmUgdGVzdCBpZiBlc3RpbWF0ZWQgdmFsdWUgc3VmZmljaWVudCAoZGVmYXVsdCA6IE5paiogPiA1KQogIAogIGZvciAoaSBpbiAxOm4pIHsKICAgIGlmKHRhYiR0cmlhbFtpXSp0YWIkbnVsbC52YWx1ZVtpXT49bWludGVzdCkgeyAgCiAgICAgIHRlc3Q8LXByb3AudGVzdCh4PXRhYiRzdWNjZXNzW2ldLG49dGFiJHRyaWFsW2ldLCBwPXRhYiRudWxsLnZhbHVlW2ldLCAKICAgICAgICAgICAgICAgICAgICAgIGFsdGVybmF0aXZlID0gImdyZWF0ZXIiKQogICAgICB0YWIkY2hpMltpXTwtcm91bmQodGVzdCRzdGF0aXN0aWMsMikKICAgICAgdGFiJHAudmFsdWVbaV08LXJvdW5kKHRlc3QkcC52YWx1ZSw1KQogICAgfSAKICB9CiAgfQogIHJldHVybih0YWIpCn0KCmBgYAoKCgoKYGBge3J9CmhjIDwtIHJlYWRSRFMoImRhdGEvY29ycHVzL2hjX215Y29ycHVzX3N0YXRlc19wYW5kLlJEUyIpCmBgYAoKCiMjIFRvcGljIGZyZXF1ZW5jZSAoV2hhdCA/KSAKCgojIyMgRnVuY3Rpb24KCmBgYHtyfQojIyMgLS0tLS0tLS0tLS0tLS0tLSB3aGF0IC0tLS0tLS0tLS0tLS0tLS0KIycgQHRpdGxlICBDb21wdXRlIHRoZSBhdmVyYWdlIHNhbGllbmNlIG9mIHRoZSB0b3BpYwojJyBAbmFtZSB3aGF0CiMnIEBkZXNjcmlwdGlvbiBjcmVhdGUgYSB0YWJsZSBhbmQgZ3JhcGhpYyBvZiB0aGUgdG9waWMKIycgQHBhcmFtIGhjIGFuIGh5cGVyY3ViZSBwcmVwYXJlZCBhcyBkYXRhLnRhYmxlCiMnIEBwYXJhbSBzdWJ0b3AgYSBzdWJ0YWcgb2YgdGhlIG1haW4gdGFnIChkZWZhdWx0ID0gTkEpCiMnIEBwYXJhbSB0aXRsZSBUaXRsZSBvZiB0aGUgZ3JhcGhpYwoKCndoYXQgPC0gZnVuY3Rpb24gKGhjID0gaHlwZXJjdWJlLAogICAgICAgICAgICAgICAgICBzdWJ0b3AgPSBOQSwKICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiV2hhdCA/IikKewogCiAgCnRhYjwtaGMKaWYgKGlzLm5hKHN1YnRvcCkpe3RhYiR3aGF0IDwtdGFiJHdoYXQgIT0iX25vXyJ9ZWxzZSB7dGFiJHdoYXQgPC0gdGFiJHdoYXQgPT0gc3VidG9wfQoKdGFiPC10YWJbLGxpc3QobmV3cyA9IHN1bShuZXdzKSksYnkgPSB3aGF0XQp0YWIkcGN0PC0xMDAqdGFiJG5ld3Mvc3VtKHRhYiRuZXdzKQoKcCA8LSBwbG90X2x5KHRhYiwKICAgICAgICAgICAgIGxhYmVscyA9IH53aGF0LAogICAgICAgICAgICAgdmFsdWVzID0gfnBjdCwKICAgICAgICAgICAgIHR5cGUgPSAncGllJykgJT4lCiAgbGF5b3V0KHRpdGxlID0gdGl0bGUsCiAgICAgICAgIHhheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpKQoKb3V0cHV0PC1saXN0KCJ0YWJsZSIgPSB0YWIsICJwbG90bHkiID1wKQoKcmV0dXJuKG91dHB1dCkKCn0KYGBgCgoKIyMjIEV4YW1wbGUKCmBgYHtyIHdoYXQgZXhhbXBsZSAsd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnJlc193aGF0IDwtIHdoYXQoaGMgPSBoYywKICAgICAgICAgICAgIHN1YnRvcCA9IE5BLAogICAgICAgICAgICAgdGl0bGUgPSAiVG9waWMgbmV3cyIpCnJlc193aGF0JHRhYmxlCnJlc193aGF0JHBsb3RseQpgYGAKCgoKCgoKCgoKCgojIyBUb3BpYyB2YXJpYXRpb24gYnkgbWVkaWEgKHdoby53aGF0KQoKVGhlIGZ1bmN0aW9uIHdoby53aGF0IGV4cGxvcmUgdGhlIHZhcmlhdGlvbiBvZiBpbnRlcmVzdCBmb3IgdGhlIHRvcGljIGluIHRoZSBkaWZmZXJlbnQgbWVkaWEgb2YgdGhlIGNvcnB1cy4KCiMjIyBGdW5jdGlvbgoKYGBge3J9CgojIyMjIC0tLS0tLS0tLS0tLS0tLS0gd2hvLndoYXQgLS0tLS0tLS0tLS0tLS0tLQojJyBAdGl0bGUgIHZpc3VhbGl6ZSB2YXJpYXRpb24gb2YgdGhlIHRvcGljIGJldHdlZW4gbWVkaWEKIycgQG5hbWUgd2hvLndoYXQKIycgQGRlc2NyaXB0aW9uIGNyZWF0ZSBhIHRhYmxlIG9mIHZhcmlhdGlvbiBvZiB0aGUgdG9waWMgYnkgbWVkaWEKIycgQHBhcmFtIGhjIGFuIGh5cGVyY3ViZSBwcmVwYXJlZCBhcyBkYXRhLnRhYmxlCiMnIEBwYXJhbSB0ZXN0IDogdmlzdWFsaXplIHRlc3QgKFRSVUUpIG9yIHNhbGllbmNlIChGQUxTRSkKIycgQHBhcmFtIG1pbnNhbXAgOiBUaHJlc2hvbGQgb2Ygc2FtcGxlIHNpemUgcmVxdWVzdGVkIGZvciBzYWxpZW5jZSBjb21wdXRhdGlvbgojJyBAcGFyYW0gbWludGVzdCBzYW1wbGUgc2l6ZSBvZiBlc3RpbWF0ZSBmb3IgY2hpLXNxdWFyZSB0ZXN0IChkZWZhdWx0ID0gNSkKIycgQHBhcmFtIHRpdGxlIFRpdGxlIG9mIHRoZSBncmFwaGljCgoKd2hvLndoYXQgPC0gZnVuY3Rpb24gKGhjID0gaHlwZXJjdWJlLAogICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgbWluc2FtcCA9IDIwLAogICAgICAgICAgICAgICAgICAgICAgbWludGVzdCA9IDUsCiAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJXaG8gc2F5cyBXaGF0ID8iKQp7CiAgCiAgdGFiPC1oYwogIHt0YWIkd2hhdCA8LXRhYiR3aGF0ICE9Il9ub18ifQogIAogIHRhYjwtdGFiWyxsaXN0KHRyaWFsID0gc3VtKG5ld3MpLHN1Y2Nlc3M9cm91bmQoc3VtKG5ld3Mqd2hhdCksMCkpLGJ5ID0gbGlzdCh3aG8pXQogIHJlZiA8LXJvdW5kKHN1bSh0YWIkc3VjY2Vzcykvc3VtKHRhYiR0cmlhbCksNCkKICB0YWIkbnVsbC52YWx1ZTwtcmVmCiAgCiAgdGFiPC10ZXN0Y2hpMih0YWJ0ZXN0PXRhYiwKICAgICAgICAgICAgICAgIG1pbnNhbXAgPSBtaW5zYW1wLAogICAgICAgICAgICAgICAgbWludGVzdCA9IG1pbnRlc3QpCiAgCiAgCiAgCiAgaWYgKHRlc3Q9PUZBTFNFKSB7dGFiJGluZGV4ID10YWIkc2FsaWVuY2UKICB0YWI8LXRhYlt0YWIkdHJpYWwgPiBtaW5zYW1wLF0KICBteWNvbDwtYnJld2VyLnBhbCg3LCJZbE9yUmQiKQogIH0gCiAgZWxzZSB7dGFiJGluZGV4PXRhYiRwLnZhbHVlCiAgdGFiPC10YWJbdGFiJHRyaWFsKnRhYiRudWxsLnZhbHVlPm1pbnRlc3QsXQogIG15Y29sPC1icmV3ZXIucGFsKDcsIlJkWWxCdSIpCiAgbXljb2xbNF08LSJsaWdodHllbGxvdyIKICB9CiAgCiAgcCA8LSBwbG90X2x5KHRhYiwKICAgICAgICAgICAgICAgeCA9IH53aG8sCiAgICAgICAgICAgICAgIHkgPSB+ZXN0aW1hdGUqMTAwLAogICAgICAgICAgICAgICBjb2xvcj0gfmluZGV4LAogICAgICAgICAgICAgICBjb2xvcnM9IG15Y29sLAogICAgICAgICAgICAgICBob3ZlcmluZm8gPSAidGV4dCIsCiAgICAgICAgICAgICAgIHRleHQgPSB+cGFzdGUoJ1NvdXJjZTogJyx3aG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxiciAvPiBUb3RhbCBuZXdzICA6ICcsIHJvdW5kKHRyaWFsLDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnIgLz4gVG9waWMgbmV3cyA6ICcsIHJvdW5kKHN1Y2Nlc3MsMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxiciAvPiAlIG9ic2VydmVkICA6ICcsIHJvdW5kKGVzdGltYXRlKjEwMCwyKSwnJScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxiciAvPiAlIGVzdGltYXRlZCA6ICcsIHJvdW5kKG51bGwudmFsdWUqMTAwLDIpLCclJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyIC8+IFNhbGllbmNlIDogJywgcm91bmQoc2FsaWVuY2UsMiksICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyIC8+IHAudmFsdWUgOiAnLCByb3VuZChwLnZhbHVlLDQpKSwKICAgICAgICAgICAgICAgdHlwZSA9ICJiYXIiKSAgJT4lCiAgICBsYXlvdXQodGl0bGUgPSB0aXRsZSwKICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiJSBuZXdzIiksCiAgICAgICAgICAgYmFybW9kZSA9ICdzdGFjaycpCiAgCiAgb3V0cHV0PC1saXN0KCJ0YWJsZSIgPSB0YWIsICJwbG90bHkiID1wKQogIAogIHJldHVybihvdXRwdXQpCiAgCn0KYGBgCgoKIyMjIEV4YW1wbGUKCgpgYGB7ciB3aG8ud2hhdCBleGFtcGxlLHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKCgoKcmVzX3dob193aGF0PC0gd2hvLndoYXQoaGM9aGMsIAogICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgbWluc2FtcCA9IDUsCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbnRlc3QgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJUb3BpYyBuZXdzIGJ5IG1lZGlhIC0gU2lnbmlmaWNhbmNlIikKcmVzX3dob193aGF0JHBsb3RseQoKYGBgCgoKCiMjIFRvcGljIHZhcmlhdGlvbiB0aHJvdWdoIHRpbWUgKHdoZW4ud2hhdCkKCgoKIyMjIEZ1bmN0aW9uCgpgYGB7cn0KIyMjIyAtLS0tLS0tLS0tLS0tLS0tIHdoZW4ud2hhdCAtLS0tLS0tLS0tLS0tLS0tCiMnIEB0aXRsZSAgdmlzdWFsaXplIHZhcmlhdGlvbiBvZiB0aGUgdG9waWMgdGhyb3VnaCB0aW1lCiMnIEBuYW1lIHdoZW4ud2hhdAojJyBAZGVzY3JpcHRpb24gY3JlYXRlIGEgdGFibGUgb2YgdmFyaWF0aW9uIG9mIHRoZSB0b3BpYyBieSBtZWRpYQojJyBAcGFyYW0gdGVzdCA6IHZpc3VhbGl6ZSB0ZXN0IChUUlVFKSBvciBzYWxpZW5jZSAoRkFMU0UpCiMnIEBwYXJhbSBtaW5zYW1wIDogVGhyZXNob2xkIG9mIHNhbXBsZSBzaXplIHJlcXVlc3RlZCBmb3Igc2FsaWVuY2UgY29tcHV0YXRpb24KIycgQHBhcmFtIG1pbnRlc3Qgc2FtcGxlIHNpemUgb2YgZXN0aW1hdGUgZm9yIGNoaS1zcXVhcmUgdGVzdCAoZGVmYXVsdCA9IDUpCiMnIEBwYXJhbSB0aXRsZSBUaXRsZSBvZiB0aGUgZ3JhcGhpYwoKCndoZW4ud2hhdCA8LSBmdW5jdGlvbiAoaGMgPSBoeXBlcmN1YmUsCiAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgIG1pbnNhbXAgPSAyMCwKICAgICAgICAgICAgICAgICAgICAgICBtaW50ZXN0ID0gNSwKICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJXaG8gc2F5cyBXaGF0ID8iKQp7CiAgCiAgdGFiPC1oYwogIHt0YWIkd2hhdCA8LXRhYiR3aGF0ICE9Il9ub18ifQogIAogIHRhYjwtdGFiWyxsaXN0KHRyaWFsID0gc3VtKG5ld3MpLHN1Y2Nlc3M9cm91bmQoc3VtKG5ld3Mqd2hhdCksMCkpLGJ5ID0gbGlzdCh3aGVuKV0KICByZWYgPC1yb3VuZChzdW0odGFiJHN1Y2Nlc3MpL3N1bSh0YWIkdHJpYWwpLDQpCiAgdGFiJG51bGwudmFsdWU8LXJlZgogIAogIHRhYjwtdGVzdGNoaTIodGFidGVzdD10YWIsCiAgICAgICAgICAgICAgICBtaW5zYW1wID0gbWluc2FtcCwKICAgICAgICAgICAgICAgIG1pbnRlc3QgPSBtaW50ZXN0KQogIAogIGlmICh0ZXN0PT1GQUxTRSkge3RhYiRpbmRleCA9dGFiJHNhbGllbmNlCiAgdGFiPC10YWJbdGFiJHRyaWFsID4gbWluc2FtcCxdCiAgbXljb2w8LWJyZXdlci5wYWwoNywiWWxPclJkIikKICB9IAogIGVsc2Uge3RhYiRpbmRleD10YWIkcC52YWx1ZQogIHRhYjwtdGFiW3RhYiR0cmlhbCp0YWIkbnVsbC52YWx1ZT5taW50ZXN0LF0KICBteWNvbDwtYnJld2VyLnBhbCg3LCJSZFlsQnUiKQogIG15Y29sWzRdPC0ibGlnaHR5ZWxsb3ciCiAgfQogIAogIAogIHAgPC0gcGxvdF9seSh0YWIsCiAgICAgICAgICAgICAgIHggPSB+YXMuY2hhcmFjdGVyKHdoZW4pLAogICAgICAgICAgICAgICB5ID0gfmVzdGltYXRlKjEwMCwKICAgICAgICAgICAgICAgY29sb3I9IH5pbmRleCwKICAgICAgICAgICAgICAgY29sb3JzPSBteWNvbCwKICAgICAgICAgICAgICAgaG92ZXJpbmZvID0gInRleHQiLAogICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCdUaW1lOiAnLHdoZW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxiciAvPiBUb3RhbCBuZXdzICA6ICcsIHJvdW5kKHRyaWFsLDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnIgLz4gVG9waWMgbmV3cyA6ICcsIHJvdW5kKHN1Y2Nlc3MsMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxiciAvPiAlIG9ic2VydmVkICA6ICcsIHJvdW5kKGVzdGltYXRlKjEwMCwyKSwnJScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxiciAvPiAlIGVzdGltYXRlZCA6ICcsIHJvdW5kKG51bGwudmFsdWUqMTAwLDIpLCclJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyIC8+IFNhbGllbmNlIDogJywgcm91bmQoc2FsaWVuY2UsMiksICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyIC8+IHAudmFsdWUgOiAnLCByb3VuZChwLnZhbHVlLDQpKSwKICAgICAgICAgICAgICAgdHlwZSA9ICJiYXIiKSAgJT4lCiAgICBsYXlvdXQodGl0bGUgPSB0aXRsZSwKICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiJSBuZXdzIiksCiAgICAgICAgICAgYmFybW9kZSA9ICdzdGFjaycpCiAgCiAgb3V0cHV0PC1saXN0KCJ0YWJsZSIgPSB0YWIsICJwbG90bHkiID1wKQogIAogIHJldHVybihvdXRwdXQpCiAgCn0KYGBgCgoKCiMjIyBFeGFtcGxlIDEgOiAyMDE5LTIwMjAgIGJ5IG1vbnRoCgoKYGBge3Igd2hlbi53aGF0IGV4YW1wbGUxLHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIE1vZGlmeSB0aW1lIHBlcmlvZCBieSBtb250aApoYzIgPC0gaGMgJT4lIG11dGF0ZSh3aGVuID0gY3V0KHdoZW4sYnJlYWtzPSJtb250aCIpKQoKCnJlc193aGVuX3doYXQ8LSB3aGVuLndoYXQoaGM9aGMyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0PVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWluc2FtcD0xMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBtaW50ZXN0PTUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiVG9waWMgbmV3cyBieSBtb250aCAtIFNpZ25pZmljYW5jZSIpCgoKcmVzX3doZW5fd2hhdCRwbG90bHkKYGBgCgoKCiMjIyBFeGFtcGxlIDIgOiAyMDIwICBieSB3ZWVrCgpgYGB7ciB3aGVuLndoYXQgZXhhbXBsZTIsd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgTW9kaWZ5IHRpbWUgcGVyaW9kIGJ5IG1vbnRoCmhjMiA8LSBoYyAlPiUgZmlsdGVyKHN1YnN0cih3aGVuLDEsNCk9PSIyMDIwIikgJT4lIG11dGF0ZSh3aGVuID0gY3V0KHdoZW4sYnJlYWtzPSJ3ZWVrIikpCgoKcmVzX3doZW5fd2hhdDwtIHdoZW4ud2hhdChoYz1oYzIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3Q9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5zYW1wPTEwLAogICAgICAgICAgICAgICAgICAgICAgICAgIG1pbnRlc3Q9NSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJUb3BpYyBuZXdzIGJ5IHdlZWsgLSBTaWduaWZpY2FuY2UiKQoKCnJlc193aGVuX3doYXQkcGxvdGx5CmBgYAoKIyMjIEV4YW1wbGUgMyA6IEphbi1NYXJjaCAyMDIwIGJ5IGRheQoKYGBge3Igd2hlbi53aGF0IGV4YW1wbGUzLHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIE1vZGlmeSB0aW1lIHBlcmlvZCBieSBtb250aApoYzIgPC0gaGMgJT4lIGZpbHRlcih3aGVuID4gYXMuRGF0ZSgiMjAyMC0wMS0wMSIpLCB3aGVuIDwgYXMuRGF0ZSgiMjAyMC0wNC0wMSIpKSAKCgpyZXNfd2hlbl93aGF0PC0gd2hlbi53aGF0KGhjPWhjMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdD1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgIG1pbnNhbXA9MTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWludGVzdD01LAogICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIlRvcGljIG5ld3MgYnkgZGF5IC0gU2lnbmlmaWNhbmNlIikKCgpyZXNfd2hlbl93aGF0JHBsb3RseQpgYGAKCgoKCiMjIFRvcGljIHZhcmlhdGlvbiB0aHJvdWdoIHNwYWNlICh3aGVyZS53aGF0KQoKCiMjIyBGdW5jdGlvbgoKYGBge3J9CgojIyMjIC0tLS0tLS0tLS0tLS0tLS0gd2hlcmUud2hhdCAtLS0tLS0tLS0tLS0tLS0tCiMnIEB0aXRsZSAgdmlzdWFsaXplIHNwYXRpYWxpemF0aW9uIG9mIHRoZSB0b3BpYyAKIycgQG5hbWUgd2hlcmUud2hhdAojJyBAZGVzY3JpcHRpb24gY3JlYXRlIGEgdGFibGUgb2YgdmFyaWF0aW9uIG9mIHRoZSB0b3BpYyBieSBtZWRpYQojJyBAcGFyYW0gaGMgYW4gaHlwZXJjdWJlIHByZXBhcmVkIGFzIGRhdGEudGFibGUKIycgQHBhcmFtIHRlc3QgOiB2aXN1YWxpemUgdGVzdCAoVFJVRSkgb3Igc2FsaWVuY2UgKEZBTFNFKQojJyBAcGFyYW0gbWluc2FtcCA6IFRocmVzaG9sZCBvZiBzYW1wbGUgc2l6ZSByZXF1ZXN0ZWQgZm9yIHNhbGllbmNlIGNvbXB1dGF0aW9uCiMnIEBwYXJhbSBtaW50ZXN0IHNhbXBsZSBzaXplIG9mIGVzdGltYXRlIGZvciBjaGktc3F1YXJlIHRlc3QgKGRlZmF1bHQgPSA1KQojJyBAcGFyYW0gbWFwIGEgbWFwIHdpdGggY29vcmRpbmF0ZXMgaW4gbGF0LWxvbmcKIycgQHBhcmFtIHByb2ogYSBwcm9qZWN0aW9uIGFjY2VwdGVkIGJ5IHBsb3RseQojJyBAcGFyYW0gdGl0bGUgVGl0bGUgb2YgdGhlIGdyYXBoaWMKCgp3aGVyZS53aGF0IDwtIGZ1bmN0aW9uIChoYyA9IGh5cGVyY3ViZSwKICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICBtaW5zYW1wID0gMjAsCiAgICAgICAgICAgICAgICAgICAgICAgIG1pbnRlc3QgPSA1LAogICAgICAgICAgICAgICAgICAgICAgICBtYXAgPSB3b3JsZF9jdHIsCiAgICAgICAgICAgICAgICAgICAgICAgIHByb2ogPSAnYXppbXV0aGFsIGVxdWFsIGFyZWEnLAogICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJXaGVyZSBzYWlkIFdoYXQgPyIpCnsKICAKICB0YWI8LWhjCiAgdGFiJHdoYXQgPC10YWIkd2hhdCAhPSJfbm9fIgogIAogIHRhYjwtdGFiWyxsaXN0KHRyaWFsID0gcm91bmQoc3VtKG5ld3MpLDApLHN1Y2Nlc3M9cm91bmQoc3VtKG5ld3Mqd2hhdCksMCkpLGJ5ID0gbGlzdCh3aGVyZTEpXQogIHJlZiA8LXJvdW5kKHN1bSh0YWIkc3VjY2Vzcykvc3VtKHRhYiR0cmlhbCksNCkKICB0YWIkbnVsbC52YWx1ZTwtcmVmCiAgCiAgdGFiPC10ZXN0Y2hpMih0YWJ0ZXN0PXRhYiwKICAgICAgICAgICAgICAgIG1pbnNhbXAgPSBtaW5zYW1wLAogICAgICAgICAgICAgICAgbWludGVzdCA9IG1pbnRlc3QpCiAgCiAgCiAgCiAgdGFiPC10YWJbb3JkZXIoLWNoaTIpLF0KICAKICAKICAKICBpZiAodGVzdD09RkFMU0UpIHt0YWIkaW5kZXggPXRhYiRzYWxpZW5jZQogIHRhYjwtdGFiW3RhYiR0cmlhbCA+IG1pbnNhbXAsXQogIG15Y29sPC1icmV3ZXIucGFsKDcsIllsT3JSZCIpCiAgfSAKICBlbHNlIHt0YWIkaW5kZXg9dGFiJHAudmFsdWUKICB0YWI8LXRhYlt0YWIkdHJpYWwqdGFiJG51bGwudmFsdWU+bWludGVzdCxdCiAgbXljb2w8LWJyZXdlci5wYWwoNywiUmRZbEJ1IikKICBteWNvbFs0XTwtImxpZ2h0eWVsbG93IgogIH0KICAKICAKICBtYXA8LW1lcmdlKG1hcCx0YWIsYWxsLng9VCxhbGwueT1GLGJ5Lng9IklTTzMiLGJ5Lnk9IndoZXJlMSIpCiAgCiAgCiAgCiAgI21hcDI8LW1hcFtpcy5uYShtYXAkcGN0KT09RixdCiAgI21hcDI8LXN0X2NlbnRyb2lkKG1hcDIpCiAgI21hcDI8LXN0X2Ryb3BfZ2VvbWV0cnkobWFwMikKICAKICAKICBnIDwtIGxpc3Qoc2hvd2ZyYW1lID0gVFJVRSwKICAgICAgICAgICAgZnJhbWVjb2xvcj0gdG9SR0IoImdyYXkyMCIpLAogICAgICAgICAgICBjb2FzdGxpbmVjb2xvciA9IHRvUkdCKCJncmF5MjAiKSwKICAgICAgICAgICAgc2hvd2xhbmQgPSBUUlVFLAogICAgICAgICAgICBsYW5kY29sb3IgPSB0b1JHQigiZ3JheTUwIiksCiAgICAgICAgICAgIHNob3djb3VudHJpZXMgPSBUUlVFLAogICAgICAgICAgICBjb3VudHJ5Y29sb3IgPSB0b1JHQigid2hpdGUiKSwKICAgICAgICAgICAgY291bnRyeXdpZHRoID0gMC4yLAogICAgICAgICAgICBwcm9qZWN0aW9uID0gbGlzdCh0eXBlID0gcHJvaikpCiAgCiAgCiAgCiAgcDwtIHBsb3RfZ2VvKG1hcCklPiUKICAgIGFkZF9tYXJrZXJzKHggPSB+bG9uLAogICAgICAgICAgICAgICAgeSA9IH5sYXQsCiAgICAgICAgICAgICAgICBzaXplcyA9IGMoMCwgMjUwKSwKICAgICAgICAgICAgICAgIHNpemUgPSB+c3VjY2VzcywKICAgICAgICAgICAgICAgICMgICAgICAgICAgICAgY29sb3I9IH5zaWduaWYsCiAgICAgICAgICAgICAgICBjb2xvciA9IH5pbmRleCwKICAgICAgICAgICAgICAgIGNvbG9ycz0gbXljb2wsCiAgICAgICAgICAgICAgICBob3ZlcmluZm8gPSAidGV4dCIsCiAgICAgICAgICAgICAgICB0ZXh0ID0gfnBhc3RlKCdMb2NhdGlvbjogJyxOQU1FLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyIC8+IFRvdGFsIG5ld3MgIDogJywgcm91bmQodHJpYWwsMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnIgLz4gVG9waWMgbmV3cyA6ICcsIHJvdW5kKHN1Y2Nlc3MsMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnIgLz4gJSBvYnNlcnZlZCAgOiAnLCByb3VuZChlc3RpbWF0ZSoxMDAsMiksJyUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyIC8+ICUgZXN0aW1hdGVkIDogJywgcm91bmQobnVsbC52YWx1ZSoxMDAsMiksJyUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyIC8+IFNhbGllbmNlIDogJywgcm91bmQoc2FsaWVuY2UsMiksICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxiciAvPiBwLnZhbHVlIDogJywgcm91bmQocC52YWx1ZSw0KSkpICU+JQogICAgCiAgICBsYXlvdXQoZ2VvID0gZywKICAgICAgICAgICB0aXRsZSA9IHRpdGxlKQogIAogIAogIAogIG91dHB1dDwtbGlzdCgidGFibGUiID0gdGFiLCAicGxvdGx5IiA9cCkKICAKICByZXR1cm4ob3V0cHV0KQogIAp9CmBgYAoKCgojIyMgRXhhbXBsZQoKCmBgYHtyIHdoZXJlLndoYXQgZXhhbXBsZSx3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbWFwPC1yZWFkUkRTKCJkYXRhL2RpY29fc3RhdGVzL3dvcmxkX2N0cl80MzI2LlJkYXRhIikKaGMyPC1oYyAlPiUgZmlsdGVyKHdoZXJlMSAhPSJfbm9fIiwgd2hlcmUyICE9Il9ub18iKSAlPiUgZmlsdGVyKHdoZXJlMSAhPSJHQlIiLCB3aGVyZTIgIT0iR0JSIikKCnJlc193aGVyZV93aGF0PC0gd2hlcmUud2hhdChoYz1oYzIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0PVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5zYW1wPTEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwID0gbWFwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbnRlc3QgPTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJUb3BpYyBuZXdzIGJ5IHN0YXRlcyAtIFNpZ25pZmljYW5jZSIpCnJlc193aGVyZV93aGF0JHBsb3RseQpgYGAKCgoKIyBTcGF0aWFsIE5ldHdvcmtzCgoKCgojIyBHZW8gbmV0d29ya3MgbW9kZWxpc2F0aW9uCgoKIyMjIEh5cGVyY3ViZSBGaWx0ZXIgKGZ1bmN0aW9uKQoKCmBgYHtyfQpoY19maWx0ZXIgPC0gZnVuY3Rpb24oZG9uID0gaGMsCiAgICAgICAgICAgICAgICAgICAgICB3aG8gPSAid2hvIiwKICAgICAgICAgICAgICAgICAgICAgIHdoZW4gPSAid2hlbiIsCiAgICAgICAgICAgICAgICAgICAgICB3aGVyZTEgPSAid2hlcmUxIiwKICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMiA9ICJ3aGVyZTIiLAogICAgICAgICAgICAgICAgICAgICAgd2d0ID0gInRhZ3MiLAogICAgICAgICAgICAgICAgICAgICAgc2VsZiA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgd2hlbl9zdGFydCA9IE5BLAogICAgICAgICAgICAgICAgICAgICAgd2hlbl9lbmQgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgIHdob19leGMgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgIHdob19pbmMgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMV9leGMgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMV9pbmMgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMl9leGMgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMl9pbmMgPSBOQSkKCiAgeyAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgCiAgICBkZjwtZGF0YS50YWJsZSh3aG8gPSBkb25bW3dob11dLAogICAgICAgICAgICAgICAgICAgd2hlbiA9IGRvbltbd2hlbl1dLAogICAgICAgICAgICAgICAgICAgd2hlcmUxID0gZG9uW1t3aGVyZTFdXSwKICAgICAgICAgICAgICAgICAgIHdoZXJlMiA9IGRvbltbd2hlcmUyXV0sCiAgICAgICAgICAgICAgICAgICB3Z3QgPSBkb25bW3dndF1dKQogICAgCiAgICAjIFNlbGVjdCB0aW1lIHBlcmlvZAogICAgICAgIGlmIChpcy5uYSh3aGVuX3N0YXJ0KT09RkFMU0UpIHsgCiAgICAgICAgZGYgPC0gZGZbd2hlbiA+PSBhcy5EYXRlKHdoZW5fc3RhcnQpLCBdfQogICAgICAgIGlmIChpcy5uYSh3aGVuX2VuZCk9PUZBTFNFKSB7IAogICAgICAgIGRmIDwtIGRmW3doZW4gPD0gYXMuRGF0ZSh3aGVuX2VuZCksIF19CiAgICAjIFNlbGVjdCB3aG8KICAgICAgICBpZiAoaXMubmEod2hvX2V4Yyk9PUZBTFNFKSB7IAogICAgICAgIGRmIDwtIGRmWyEod2hvICVpbiUgd2hvX2V4YyksIF19CiAgICAgICAgaWYgKGlzLm5hKHdob19pbmMpPT1GQUxTRSkgeyAKICAgICAgICBkZiA8LSBkZlsod2hvICVpbiUgd2hvX2luYyksIF19CiAgICAjIFNlbGVjdCB3aGVyZTEKICAgICAgICBpZiAoaXMubmEod2hlcmUxX2V4Yyk9PUZBTFNFKSB7IAogICAgICAgIGRmIDwtIGRmWyEod2hlcmUxICVpbiUgd2hlcmUxX2V4YyksIF19CiAgICAgICAgaWYgKGlzLm5hKHdoZXJlMV9pbmMpPT1GQUxTRSkgeyAKICAgICAgICBkZiA8LSBkZlsod2hlcmUxICVpbiUgd2hlcmUxX2luYyksIF19CiAgICAjIFNlbGVjdCB3aGVyZTIKICAgICAgICBpZiAoaXMubmEod2hlcmUyX2V4Yyk9PUZBTFNFKSB7IAogICAgICAgIGRmIDwtIGRmWyEod2hlcmUyICVpbiUgd2hlcmUyX2V4YyksIF19CiAgICAgICAgaWYgKGlzLm5hKHdoZXJlMl9pbmMpPT1GQUxTRSkgeyAKICAgICAgICBkZiA8LSBkZlsod2hlcmUyICVpbiUgd2hlcmUyX2luYyksIF19CiAgICAjIGVsaW1pbmF0ZSBpbnRlcm5hbCBsaW5rcwogICAgICAgaWYgKHNlbGY9PUZBTFNFKSB7IAogICAgICAgIGRmIDwtIGRmWyh3aGVyZTEgIT0gd2hlcmUyKSwgXX0KICAgIHJldHVybihkZikKICAKfQpgYGAKCiMjIyBNYXRyaXggYnVpbGRlciAoZnVuY3Rpb24pCgoKCgpgYGB7cn0KYnVpbGRfaW50IDwtIGZ1bmN0aW9uKGRvbiA9IGRvbiwgICAgICAgIyBhIGRhdGFmcmFtZSB3aXRoIGNvbHVtbnMgaSwgaiAsIEZpagogICAgICAgICAgICAgICAgICAgICAgaSA9ICJ3aGVyZTEiLAogICAgICAgICAgICAgICAgICAgICAgaiA9ICJ3aGVyZTIiLAogICAgICAgICAgICAgICAgICAgICAgRmlqID0gIndndCIsCiAgICAgICAgICAgICAgICAgICAgICBzMSA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgczIgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgIG4xID0gMiwKICAgICAgICAgICAgICAgICAgICAgIG4yID0gMiwKICAgICAgICAgICAgICAgICAgICAgIGsgPSAxKQoKeyAgCiAgZGY8LWRhdGEudGFibGUoaT1kb25bW2ldXSxqPWRvbltbal1dLEZpaj1kb25bW0Zpal1dKQogIGludCA8LWRmWywuKEZpaj1zdW0oRmlqKSksLihpLGopXQogIGludDwtZGNhc3QoaW50LGZvcm11bGEgPSBpfmosZmlsbCA9IDApCiAgbWF0PC1hcy5tYXRyaXgoaW50WywtMV0pCiAgcm93Lm5hbWVzKG1hdCk8LWludCRpCiAgbWF0PC1tYXRbYXBwbHkobWF0LDEsc3VtKT49czEsYXBwbHkobWF0LDIsc3VtKT49czIgXQogIG0wPC1tYXQKICBtMFttMDxrXTwtMAogIG0wW20wPj1rXTwtMQogIG1hdDwtbWF0W2FwcGx5KG0wLDEsc3VtKT49bjEsYXBwbHkobTAsMixzdW0pPj1uMiBdCiAgaW50PC1yZXNoYXBlMjo6bWVsdChtYXQpCiAgbmFtZXMoaW50KSA8LWMoImkiLCJqIiwiRmlqIikKICByZXR1cm4oaW50KQp9CgpgYGAKCgojIyMgUmFuZG9tIG1vZGVsIChmdW5jdGlvbikKCgpgYGB7cn0KCnJhbmRfaW50IDwtIGZ1bmN0aW9uKGludCA9IGludCwgIyBBIHRhYmxlIHdpdGggY29sdW1ucyBpLCBqIEZpagogICAgICAgICAgICAgICAgICAgICBtYXhzaXplID0gMTAwMDAwLAogICAgICAgICAgICAgICAgICAgICBkaWFnICAgID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgIHJlc2lkICAgPSBGQUxTRSkgewogICAgIyBFbGltaW5hdGUgZGlhZ29uYWwgPwogICAgaWYgKGRpYWc9PUZBTFNFKSB7IAogICAgICAgIGludCA8LSBpbnRbYXMuY2hhcmFjdGVyKGludCRpKSAhPSBhcy5jaGFyYWN0ZXIoaW50JGopLCBdfQogIAogICAgIyBDb21wdXRlIG1vZGVsIGlmIHNpemUgbm90IHRvbyBsYXJnZQogICAgaWYgKGRpbShpbnQpWzFdIDwgbWF4c2l6ZSkgewogICAgICAgIyBQcm9jZWVkIHRvIHBvaXNzb24gcmVncmVzc2lvbiBtb2RlbAogICAgICAgbW9kIDwtIGdsbSggZm9ybXVsYSA9IEZpaiB+IGkgKyBqLGZhbWlseSA9ICJwb2lzc29uIiwgZGF0YSA9IGludCkKICAKICAgICAgICMgQWRkIHJlc2lkdWFscyBpZiByZXF1ZXN0ZWQKICAgICAgIGlmKHJlc2lkID09IFRSVUUpICAgeyAKICAgICAgICAgICMgQWRkIGVzdGltYXRlcwogICAgICAgICAgaW50JEVpaiA8LSBtb2QkZml0dGVkLnZhbHVlcwoKICAgICAgICAgICMgQWRkIGFic29sdXRlIHJlc2lkdWFscwogICAgICAgICAgaW50JFJhYnNfaWogPC0gaW50JEZpai1pbnQkRWlqCgogICAgICAgICAgIyBBZGQgcmVsYXRpdmUgcmVzaWR1YWxzCiAgICAgICAgICBpbnQkUnJlbF9paiA8LSBpbnQkRmlqL2ludCRFaWoKCiAgICAgICAgICAjIEFkZCBjaGktc3F1YXJlIHJlc2lkdWFscwogICAgICAgICAgaW50JFJjaGlfaWogPC0gIChpbnQkUmFic19paikqKjIgLyBpbnQkRWlqCiAgICAgICAgICBpbnQkUmNoaV9paltpbnQkUmFic19pajwwXTwtIC1pbnQkUmNoaV9paltpbnQkUmFic19pajwwXQogICAgICAgICAgfQogICAgICAgICAKICAgIH0gZWxzZSB7IHBhc3RlICgiVGFibGUgPiAxMDAwMDAgLSAgXG4gCiAgICAgICAgICAgICAgICAgICAgIG1vZGlmeSBtYXhzaXplID0gIHBhcmFtZXRlciBcbgogICAgICAgICAgICAgICAgICAgICBpZiB5b3UgYXJlIHN1cmUgdGhhdCB5b3VyIGNvbXB1dGVyIGNhbiBkbyBpdCAhIil9CiAgIyBFeHBvcnQgcmVzdWx0cwogIGludCRpPC1hcy5jaGFyYWN0ZXIoaW50JGkpCiAgaW50JGo8LWFzLmNoYXJhY3RlcihpbnQkaikKICByZXR1cm4oaW50KQogIAogfQoKYGBgCgoKIyMjIFZpc3VhbGl6ZSBuZXR3b3JrIChmdW5jdGlvbikKCmBgYHtyfQpnZW9fbmV0d29yazwtIGZ1bmN0aW9uKGRvbiA9IGRvbiwKICAgICAgICAgICAgICAgICAgICAgICBmcm9tID0gImkiLAogICAgICAgICAgICAgICAgICAgICAgICB0byA9ICJqIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAiRmlqIiwKICAgICAgICAgICAgICAgICAgICAgICAgbWluc2l6ZSA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgIG1heHNpemUgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9ICJGaWoiLAogICAgICAgICAgICAgICAgICAgICAgICBtaW50ZXN0ID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgbG9vcHMgID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJOZXR3b3JrIikKCnsKaW50PC1kYXRhLmZyYW1lKGkgPSBhcy5jaGFyYWN0ZXIoZG9uWyxmcm9tXSksCiAgICAgICAgICAgICAgICBqID0gYXMuY2hhcmFjdGVyKGRvblssdG9dKSwKICAgICAgICAgICAgICAgIHNpemUgPSBkb25bLHNpemVdLAogICAgICAgICAgICAgICAgdGVzdCA9IGRvblssdGVzdF0KICAgICAgICAgICAgICAgICkKaWYgKGlzLm5hKG1pbnNpemUpPT1GQUxTRSkge2ludCA9aW50W2ludCRzaXplID49IG1pbnNpemUsXX0gCmlmIChpcy5uYShtYXhzaXplKT09RkFMU0UpIHtpbnQgPWludFtpbnQkc2l6ZSA8PSBtYXhzaXplLF19IAppZiAoaXMubmEobWludGVzdCk9PUZBTFNFKSB7aW50ID1pbnRbaW50JHRlc3QgPj0gbWludGVzdCxdfQoKbm9kZXM8LWRhdGEuZnJhbWUoY29kZSA9IHVuaXF1ZShjKGludCRpLGludCRqKSkpCm5vZGVzJGNvZGU8LWFzLmNoYXJhY3Rlcihub2RlcyRjb2RlKQpub2RlcyRpZDwtMTpsZW5ndGgobm9kZXMkY29kZSkKbm9kZXMkbGFiZWw8LW5vZGVzJGNvZGUKbm9kZXMkY29sb3IgPC0iZ3JheSIKbm9kZXMkY29sb3Jbbm9kZXMkY29kZSAlaW4lIGludCRqXTwtInJlZCIKCgojIEFkanVzdCBlZGdlIGNvZGVzCmVkZ2VzIDwtIGludCAlPiUgbXV0YXRlKHdpZHRoID0gNSs1KnNpemUgLyBtYXgoc2l6ZSkpICU+JQogICAgICAgICAgICAgICAgbGVmdF9qb2luKG5vZGVzICU+JSBzZWxlY3QoaT1jb2RlLCBmcm9tID0gaWQpKSAlPiUgIAogICAgICAgICAgICAgICAgbGVmdF9qb2luKG5vZGVzICU+JSBzZWxlY3Qoaj1jb2RlLCB0byA9IGlkICkpIAoKIyBjb21wdXRlIG5vZGVzaXplCnRvdGk8LWludCAlPiUgZ3JvdXBfYnkoaSkgJT4lIHN1bW1hcml6ZShzaXplID1zdW0oc2l6ZSkpICU+JSBzZWxlY3QgKGNvZGU9aSxzaXplKQp0b3RqPC1pbnQgJT4lIGdyb3VwX2J5KGopICU+JSBzdW1tYXJpemUoc2l6ZSA9c3VtKHNpemUpKSAlPiUgc2VsZWN0IChjb2RlPWosc2l6ZSkKdG90PC1yYmluZCh0b3RpLHRvdGopCnRvdDwtdW5pcXVlKHRvdCkKdG90JGNvZGU8LWFzLmZhY3Rvcih0b3QkY29kZSkKbm9kZXMgPC0gbGVmdF9qb2luKG5vZGVzLHRvdCkgJT4lIG11dGF0ZSh2YWx1ZSA9IDEgKzUqc3FydChzaXplL21heChzaXplKSkpCgoKI3NlbF9ub2RlcyA8LW5vZGVzICU+JSBmaWx0ZXIoY29kZSAlaW4lIHVuaXF1ZShjKHNlbF9lZGdlcyRpLHNlbF9lZGdlcyRqKSkpCgojIGVsaW1pbmF0ZSBsb29wcwoKaWYobG9vcHMgPT0gRkFMU0UpIHtlZGdlcyA8LSBlZGdlc1tlZGdlcyRmcm9tIDwgZWRnZXMkdG8sXX0KCm5ldDwtIHZpc05ldHdvcmsobm9kZXMsIAogICAgICAgICAgICAgICAgICBlZGdlcywgCiAgICAgICAgICAgICAgICAgIG1haW4gPSB0aXRsZSwKaGVpZ2h0ID0gIjEwMDBweCIsIAogICAgICAgICAgICAgICAgICB3aWR0aCA9ICI3MCUiKSAgICU+JSAgIAogICB2aXNOb2RlcyhzY2FsaW5nID1saXN0KG1pbiA9MjAsIG1heD02MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWw9bGlzdChtaW49MjAsbWF4PTgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4VmlzaWJsZSA9IDIwKSkpJT4lCiAgdmlzRWRnZXMoc2NhbGluZyA9IGxpc3QobWluPTIwLG1heD02MCkpJT4lCiAgICAgICB2aXNPcHRpb25zKGhpZ2hsaWdodE5lYXJlc3QgPSBUUlVFLAogICAgICMgICAgICAgICAgICAgICBzZWxlY3RlZEJ5ID0gImdyb3VwIiwgCiAgICAjICAgICAgICAgICAgICAgbWFuaXB1bGF0aW9uID0gVFJVRSwKICAgICAgICAgICAgICAgICAgbm9kZXNJZFNlbGVjdGlvbiA9IFRSVUUpICU+JQogICAgICAgIHZpc0ludGVyYWN0aW9uKG5hdmlnYXRpb25CdXR0b25zID0gVFJVRSkgJT4lCiAgICAgICAgIHZpc0xlZ2VuZCgpICU+JQogICAgICB2aXNJZ3JhcGhMYXlvdXQobGF5b3V0ID0ibGF5b3V0LmZydWNodGVybWFuLnJlaW5nb2xkIixzbW9vdGggPSBUUlVFKQoKbmV0CiByZXR1cm4obmV0KQogfSAKCmBgYAoKCiMjIEFwcGxpY2F0aW9uCgoKCmBgYHtyfQojIExvYWQgY29tcGxldGUgaHlwZXJjdWJlCmhjIDwtIHJlYWRSRFMoImRhdGEvY29ycHVzL2hjX215Y29ycHVzX3N0YXRlc19wYW5kLlJEUyIpCmhjIDwtIGhjICU+JSBmaWx0ZXIod2hlcmUxICE9Il9ub18iLCB3aGVyZTIgIT0gIl9ub18iKQoKIyBFbGltaW5hdGUgbm9uIGZvcmVpZ24gbmV3cyAKaGM8LWhjW2hjJHdoZXJlMSAhPSBzdWJzdHIod2hvLDQsNiksXQpoYzwtaGNbaGMkd2hlcmUyICE9IHN1YnN0cih3aG8sNCw2KSxdCgojIEFkZCBjb21wbGV0ZSBsYWJlbHMKbWFwPC1yZWFkUkRTKCJkYXRhL2RpY29fc3RhdGVzL3dvcmxkX21hcF80MzI2LlJkYXRhIikKbGFiczwtc3RfZHJvcF9nZW9tZXRyeShtYXApCmxhYnM8LWxhYnNbLGMoMSw0KV0KCiMgU2hvcnRlbiB0aGUgbmFtZSBvZiBVU0EKbGFicyROQU1FW2xhYnMkSVNPMz09IlVTQSJdPC0iVS5TLkEuIgoKCm5hbWVzKGxhYnMpPC1jKCJ3aGVyZTEiLCJnZW9mcjEiKQpoYzwtbGVmdF9qb2luKGhjLGxhYnMpCm5hbWVzKGxhYnMpPC1jKCJ3aGVyZTIiLCJnZW9mcjIiKQpoYzwtbGVmdF9qb2luKGhjLGxhYnMpCgpoY19nZW9fZ2VvIDwtIGhjCmBgYAoKCiMjIyBSZWZlcmVuY2UgbmV0d29yawoKCgpgYGB7cn0KaGM8LWhjX2dlb19nZW8gCgoKCmhjPC1oY19maWx0ZXIoZG9uID0gaGMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2d0ID0gIm5ld3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMSA9ICJnZW9mcjEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMiA9ICJnZW9mcjIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoZXJlMV9leGMgPSBjKCJfbm9fIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2hlcmUyX2V4YyA9IGMoIl9ub18iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxmID0gRkFMU0UKICAgICAgICAgICAgICAgICAgICAgICAgICAgKQoKaW50IDwtIGJ1aWxkX2ludChkb24gPSBoYywKICAgICAgICAgICAgICAgICBzMT0yLAogICAgICAgICAgICAgICAgIHMyPTIsCiAgICAgICAgICAgICAgICAgbjE9MSwKICAgICAgICAgICAgICAgICBuMj0xLAogICAgICAgICAgICAgICAgIGs9MCkKCm1vZDwtcmFuZF9pbnQoaW50LAogICAgICAgICAgICAgIHJlc2lkID0gVFJVRSwKICAgICAgICAgICAgICBkaWFnID0gRkFMU0UpCgoKbmV0d29yazwtIGdlb19uZXR3b3JrKG1vZCwKICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAiRmlqIiwKICAgICAgICAgICAgICAgICAgICAgIG1pbnNpemUgPSAxLAogICAgICAgICAgICAgICAgICAgICAgdGVzdCA9ICJSY2hpX2lqIiwKICAgICAgICAgICAgICAgICAgICAgIG1pbnRlc3QgPSAzLjg0KQpuZXR3b3JrCgpgYGAKCgo=